Merge remote-tracking branch 'origin/GP-3823_Dan_traceRmiWindowsLaunchers--SQUASHED'

This commit is contained in:
Ryan Kurtz
2023-11-28 11:03:24 -05:00
33 changed files with 1206 additions and 1303 deletions
@@ -1,8 +1,7 @@
##VERSION: 2.0 ##VERSION: 2.0
##MODULE IP: Apache License 2.0 ##MODULE IP: Apache License 2.0
Module.manifest||GHIDRA||||END| Module.manifest||GHIDRA||||END|
src/javaprovider/def/javaprovider.def||GHIDRA||||END| data/debugger-launchers/local-dbgeng.bat||GHIDRA||||END|
src/javaprovider/rc/javaprovider.rc||GHIDRA||||END|
src/main/py/LICENSE||GHIDRA||||END| src/main/py/LICENSE||GHIDRA||||END|
src/main/py/README.md||GHIDRA||||END| src/main/py/README.md||GHIDRA||||END|
src/main/py/pyproject.toml||GHIDRA||||END| src/main/py/pyproject.toml||GHIDRA||||END|
@@ -0,0 +1,31 @@
::@title dbgeng
::@desc <html><body width="300px">
::@desc <h3>Launch with <tt>dbgeng</tt> (in a Python interpreter)</h3>
::@desc <p>This will launch the target on the local machine using <tt>dbgeng.dll</tt>. Typically,
::@desc Windows systems have this library pre-installed, but it may have limitations, e.g., you
::@desc cannot use <tt>.server</tt>. For the full capabilities, you must install WinDbg.</p>
::@desc <p>Furthermore, you must have Python 3 installed on your system, and it must have the
::@desc <tt>pybag</tt> and <tt>protobuf</tt> packages installed.</p>
::@desc </body></html>
::@menu-group local
::@icon icon.debugger
::@help TraceRmiLauncherServicePlugin#dbgeng
::@env OPT_PYTHON_EXE:str="python" "Path to python" "The path to the Python 3 interpreter. Omit the full path to resolve using the system PATH."
:: Use env instead of args, because "all args except first" is terrible to implement in batch
::@env OPT_TARGET_IMG:str="" "Image" "The target binary executable image"
::@env OPT_TARGET_ARGS:str="" "Arguments" "Command-line arguments to pass to the target"
@echo off
if exist "%GHIDRA_HOME%\ghidra\.git\" (
set PYTHONPATH=%GHIDRA_HOME%\ghidra\Ghidra\Debug\Debugger-agent-dbgeng\build\pypkg\src;%GHIDRA_HOME%\ghidra\Ghidra\Debug\Debugger-rmi-trace\build\pypkg\src;%PYTHONPATH%
) else if exist "%GHIDRA_HOME%\.git\" (
set PYTHONPATH=%GHIDRA_HOME%\Ghidra\Debug\Debugger-agent-dbgeng\build\pypkg\src;%GHIDRA_HOME%\Ghidra\Debug\Debugger-rmi-trace\build\pypkg\src;%PYTHONPATH%
) else (
set PYTHONPATH=%GHIDRA_HOME%\Ghidra\Debug\Debugger-agent-dbgeng\pypkg\src;%GHIDRA_HOME%\Ghidra\Debug\Debugger-rmi-trace\pypkg\src;%PYTHONPATH%
)
echo PYTHONPATH is %PYTHONPATH%
echo bat OPT_TARGET_IMG is [%OPT_TARGET_IMG%]
"%OPT_PYTHON_EXE%" -i ..\support\local-dbgeng.py
@@ -0,0 +1,30 @@
## ###
# 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.
##
import os
from ghidradbg.commands import *
ghidra_trace_connect(os.getenv('GHIDRA_TRACE_RMI_ADDR'))
args = os.getenv('OPT_TARGET_ARGS')
if args:
args = ' ' + args
ghidra_trace_create(os.getenv('OPT_TARGET_IMG') + args, start_trace=False)
ghidra_trace_start(os.getenv('OPT_TARGET_IMG'))
ghidra_trace_sync_enable()
# TODO: HACK
dbg().wait()
repl()
@@ -1,224 +0,0 @@
/* ###
* 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.
*/
#define INITGUID
#include <engextcpp.hpp>
#include <jni.h>
#include "resource.h"
#define CHECK_RESULT(x, y) do { \
HRESULT hr = (x); \
if (hr != S_OK) { \
fprintf(stderr, "HRESULT of %s = %x\n", ##x, hr); \
return y; \
} \
} while (0)
class EXT_CLASS : public ExtExtension {
public:
virtual HRESULT Initialize();
virtual void Uninitialize();
//virtual void OnSessionAccessible(ULONG64 Argument);
EXT_COMMAND_METHOD(java_add_cp);
EXT_COMMAND_METHOD(java_set);
EXT_COMMAND_METHOD(java_get);
EXT_COMMAND_METHOD(java_run);
void run_command(PCSTR name);
};
EXT_DECLARE_GLOBALS();
JavaVM* jvm = NULL;
JNIEnv* env = NULL;
jclass clsCommands = NULL;
char JDK_JVM_DLL_PATH[] = "\\jre\\bin\\server\\jvm.dll";
char JRE_JVM_DLL_PATH[] = "\\bin\\server\\jvm.dll";
typedef jint (_cdecl *CreateJavaVMFunc)(JavaVM**, void**, void*);
HRESULT EXT_CLASS::Initialize() {
HRESULT result = ExtExtension::Initialize();
if (result != S_OK) {
return result;
}
char* env_java_home = getenv("JAVA_HOME");
if (env_java_home == NULL) {
fprintf(stderr, "JAVA_HOME is not set\n");
fflush(stderr);
return E_FAIL;
}
char* java_home = strdup(env_java_home);
size_t home_len = strlen(java_home);
if (java_home[home_len - 1] == '\\') {
java_home[home_len - 1] = '\0';
}
size_t full_len = home_len + sizeof(JDK_JVM_DLL_PATH);
char* full_path = new char[full_len];
HMODULE jvmDll = NULL;
// Try the JRE path first;
strcpy_s(full_path, full_len, java_home);
strcat_s(full_path, full_len, JRE_JVM_DLL_PATH);
fprintf(stderr, "Trying to find jvm.dll at %s\n", full_path);
fflush(stderr);
jvmDll = LoadLibraryA(full_path);
if (jvmDll == NULL) {
// OK, then try the JDK path
strcpy_s(full_path, full_len, java_home);
strcat_s(full_path, full_len, JDK_JVM_DLL_PATH);
fprintf(stderr, "Trying to find jvm.dll at %s\n", full_path);
fflush(stderr);
jvmDll = LoadLibraryA(full_path);
}
free(full_path);
free(java_home);
if (jvmDll == NULL) {
fprintf(stderr, "Could not find the jvm.dll\n");
fflush(stderr);
return E_FAIL;
}
fprintf(stderr, "Found it!\n");
fflush(stderr);
JavaVMOption options[2];
JavaVMInitArgs vm_args = { 0 };
vm_args.version = JNI_VERSION_1_8;
vm_args.nOptions = sizeof(options)/sizeof(options[0]);
vm_args.options = options;
options[0].optionString = "-Xrs";
options[1].optionString = "-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005";
vm_args.ignoreUnrecognized = false;
CreateJavaVMFunc create_jvm = NULL;
//create_jvm = JNI_CreateJavaVM;
create_jvm = (CreateJavaVMFunc) GetProcAddress(jvmDll, "JNI_CreateJavaVM");
jint jni_result = create_jvm(&jvm, (void**)&env, &vm_args);
if (jni_result != JNI_OK) {
jvm = NULL;
fprintf(stderr, "Could not initialize JVM: %d: ", jni_result);
switch (jni_result) {
case JNI_ERR:
fprintf(stderr, "unknown error");
break;
case JNI_EDETACHED:
fprintf(stderr, "thread detached from the VM");
break;
case JNI_EVERSION:
fprintf(stderr, "JNI version error");
break;
case JNI_ENOMEM:
fprintf(stderr, "not enough memory");
break;
case JNI_EEXIST:
fprintf(stderr, "VM already created");
break;
case JNI_EINVAL:
fprintf(stderr, "invalid arguments");
break;
}
fprintf(stderr, "\n");
fflush(stderr);
return E_FAIL;
}
HMODULE hJavaProviderModule = GetModuleHandle(TEXT("javaprovider"));
HRSRC resCommandsClassfile = FindResource(hJavaProviderModule, MAKEINTRESOURCE(IDR_CLASSFILE1), TEXT("Classfile"));
HGLOBAL gblCommandsClassfile = LoadResource(hJavaProviderModule, resCommandsClassfile);
LPVOID lpCommandsClassfile = LockResource(gblCommandsClassfile);
DWORD szCommandsClassfile = SizeofResource(hJavaProviderModule, resCommandsClassfile);
clsCommands = env->DefineClass(
"javaprovider/Commands", NULL, (jbyte*) lpCommandsClassfile, szCommandsClassfile
);
if (clsCommands == NULL) {
fprintf(stderr, "Could not define Commands class\n");
if (env->ExceptionCheck()) {
env->ExceptionDescribe();
env->ExceptionClear();
return E_FAIL;
}
}
return S_OK;
}
void EXT_CLASS::Uninitialize() {
if (jvm != NULL) {
jvm->DestroyJavaVM();
}
ExtExtension::Uninitialize();
}
void EXT_CLASS::run_command(PCSTR name) {
// TODO: Throw an exception during load, then!
if (jvm == NULL) {
Out("javaprovider extension did not load properly.\n");
return;
}
if (clsCommands == NULL) {
Out("javaprovider extension did not load properly.\n");
return;
}
PCSTR args = GetRawArgStr();
jmethodID mthCommand = env->GetStaticMethodID(clsCommands, name, "(Ljava/lang/String;)V");
if (mthCommand == NULL) {
Out("INTERNAL ERROR: No such command: %s\n", name);
return;
}
jstring argsStr = env->NewStringUTF(args);
if (argsStr == NULL) {
Out("Could not create Java string for arguments.\n");
return;
}
env->CallStaticVoidMethod(clsCommands, mthCommand, argsStr);
env->DeleteLocalRef(argsStr);
if (env->ExceptionCheck()) {
Out("Exception during javaprovider command:\n");
env->ExceptionDescribe(); // TODO: Send this to output callbacks, not console.
env->ExceptionClear();
}
}
EXT_COMMAND(java_add_cp, "Add an element to the class path", "{{custom}}") {
run_command("java_add_cp");
}
EXT_COMMAND(java_set, "Set a Java system property", "{{custom}}") {
run_command("java_set");
}
EXT_COMMAND(java_get, "Get a Java system property", "{{custom}}") {
run_command("java_get");
}
EXT_COMMAND(java_run, "Execute the named java class", "{{custom}}") {
run_command("java_run");
}
#define JNA extern "C" __declspec(dllexport)
JNA HRESULT createClient(PDEBUG_CLIENT* client) {
return g_ExtInstance.m_Client->CreateClient(client);
}
@@ -1,13 +0,0 @@
EXPORTS
; For ExtCpp
DebugExtensionInitialize
DebugExtensionUninitialize
DebugExtensionNotify
help
; My Commands
java_add_cp
java_set
java_get
java_run
@@ -1,16 +0,0 @@
/* ###
* 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.
*/
#include <Windows.h>
@@ -1,31 +0,0 @@
/* ###
* 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.
*/
//{{NO_DEPENDENCIES}}
// Microsoft Visual C++ generated include file.
// Used by javaprovider.rc
//
#define IDR_CLASSFILE1 101
// Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE 102
#define _APS_NEXT_COMMAND_VALUE 40001
#define _APS_NEXT_CONTROL_VALUE 1001
#define _APS_NEXT_SYMED_VALUE 101
#endif
#endif
@@ -19,6 +19,7 @@ import os.path
import socket import socket
import time import time
import sys import sys
import re
from ghidratrace import sch from ghidratrace import sch
from ghidratrace.client import Client, Address, AddressRange, TraceObject from ghidratrace.client import Client, Address, AddressRange, TraceObject
@@ -185,7 +186,7 @@ def compute_name(progname=None):
progname = buffer.decode('utf-8') progname = buffer.decode('utf-8')
except Exception: except Exception:
return 'pydbg/noname' return 'pydbg/noname'
return 'pydbg/' + progname.split('/')[-1] return 'pydbg/' + re.split(r'/|\\', progname)[-1]
def start_trace(name): def start_trace(name):
@@ -1305,3 +1306,32 @@ def ghidra_util_wait_stopped(timeout=1):
def dbg(): def dbg():
return util.get_debugger() return util.get_debugger()
SHOULD_WAIT = ['GO', 'STEP_BRANCH', 'STEP_INTO', 'STEP_OVER']
def repl():
print("This is the dbgeng.dll (WinDbg) REPL. To drop to Python3, press Ctrl-C.")
while True:
# TODO: Implement prompt retrieval in PR to pybag?
print('dbg> ', end='')
try:
cmd = input().strip()
if not cmd:
continue
dbg().cmd(cmd, quiet=False)
stat = dbg().exec_status()
if stat != 'BREAK':
dbg().wait()
else:
pass
#dbg().dispatch_events()
except KeyboardInterrupt as e:
print("")
print("You have left the dbgeng REPL and are now at the Python3 interpreter.")
print("use repl() to re-enter.")
return
except:
# Assume cmd() has already output the error
pass
@@ -383,7 +383,7 @@ def interrupt():
@REGISTRY.method(action='step_into') @REGISTRY.method(action='step_into')
def step_into(thread: sch.Schema('Thread'), n: ParamDesc(int, display='N')=1): def step_into(thread: sch.Schema('Thread'), n: ParamDesc(int, display='N')=1):
"""Step on instruction exactly.""" """Step one instruction exactly."""
find_thread_by_obj(thread) find_thread_by_obj(thread)
dbg().stepi(n) dbg().stepi(n)
@@ -511,7 +511,7 @@ def write_mem(process: sch.Schema('Process'), address: Address, data: bytes):
@REGISTRY.method @REGISTRY.method
def write_reg(frame: sch.Schema('Frame'), name: str, value: bytes): def write_reg(frame: sch.Schema('StackFrame'), name: str, value: bytes):
"""Write a register.""" """Write a register."""
util.select_frame() util.select_frame()
nproc = pydbg.selected_process() nproc = pydbg.selected_process()
@@ -1,315 +0,0 @@
/* ###
* 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.
*/
#include <stdlib.h>
#include <string.h>
#define INITGUID
#include <dbgeng.h>
#include <Windows.h>
#include <jni.h>
JavaVM* jvm = NULL;
JNIEnv* env = NULL;
char JDK_JVM_DLL_PATH[] = "\\jre\\bin\\server\\jvm.dll";
char JRE_JVM_DLL_PATH[] = "\\bin\\server\\jvm.dll";
char MAIN_CLASS[] = "sctldbgeng/sctl/DbgEngSctlServer";
char CP_PREFIX[] = "-Djava.class.path=";
typedef jint (_cdecl *CreateJavaVMFunc)(JavaVM**, void**, void*);
#define CHECK_RC(v, f, x) do { \
HRESULT ___hr = (x); \
if (___hr < 0) { \
fprintf(stderr, "FAILED on line %d: HRESULT=%08x\n", __LINE__, ___hr); \
goto f; \
} else if (___hr == S_OK) { \
v = 1; \
} else { \
v = 0; \
} \
} while (0)
#if 0
class MyEventCallbacks : public DebugBaseEventCallbacks {
public:
STDMETHOD_(ULONG, AddRef)(THIS) {
InterlockedIncrement(&m_ulRefCount);
return m_ulRefCount;
}
STDMETHOD_(ULONG, Release)(THIS) {
ULONG ulRefCount = InterlockedDecrement(&m_ulRefCount);
if (m_ulRefCount == 0) {
delete this;
}
return ulRefCount;
}
STDMETHOD(GetInterestMask)(_Out_ PULONG Mask) {
*Mask = DEBUG_EVENT_CREATE_PROCESS | DEBUG_EVENT_CREATE_THREAD;
return S_OK;
}
STDMETHOD(CreateProcess)(
THIS_
_In_ ULONG64 ImageFileHandle,
_In_ ULONG64 Handle,
_In_ ULONG64 BaseOffset,
_In_ ULONG ModuleSize,
_In_ PCSTR ModuleName,
_In_ PCSTR ImageName,
_In_ ULONG CheckSum,
_In_ ULONG TimeDateStamp,
_In_ ULONG64 InitialThreadHandle,
_In_ ULONG64 ThreadDataOffset,
_In_ ULONG64 StartOffset
) {
UNREFERENCED_PARAMETER(ImageFileHandle);
UNREFERENCED_PARAMETER(Handle);
UNREFERENCED_PARAMETER(BaseOffset);
UNREFERENCED_PARAMETER(ModuleSize);
UNREFERENCED_PARAMETER(ModuleName);
UNREFERENCED_PARAMETER(ImageName);
UNREFERENCED_PARAMETER(CheckSum);
UNREFERENCED_PARAMETER(TimeDateStamp);
UNREFERENCED_PARAMETER(InitialThreadHandle);
UNREFERENCED_PARAMETER(ThreadDataOffset);
UNREFERENCED_PARAMETER(StartOffset);
return DEBUG_STATUS_BREAK;
}
STDMETHOD(CreateThread)(
THIS_
_In_ ULONG64 Handle,
_In_ ULONG64 DataOffset,
_In_ ULONG64 StartOffset
) {
UNREFERENCED_PARAMETER(Handle);
UNREFERENCED_PARAMETER(DataOffset);
UNREFERENCED_PARAMETER(StartOffset);
return DEBUG_STATUS_BREAK;
}
private:
ULONG m_ulRefCount = 0;
};
int main_exp00(int argc, char** argv) {
PDEBUG_CLIENT5 pClient5 = NULL;
PDEBUG_CONTROL4 pControl4 = NULL;
PDEBUG_SYMBOLS3 pSymbols3 = NULL;
int ok = 0;
CHECK_RC(ok, EXIT, DebugCreate(IID_IDebugClient5, (PVOID*) &pClient5));
CHECK_RC(ok, EXIT, pClient5->QueryInterface(IID_IDebugControl4, (PVOID*) &pControl4));
CHECK_RC(ok, EXIT, pClient5->QueryInterface(IID_IDebugSymbols3, (PVOID*) &pSymbols3));
pClient5->SetEventCallbacks(new MyEventCallbacks());
CHECK_RC(ok, EXIT, pControl4->Execute(DEBUG_OUTCTL_ALL_CLIENTS, ".create notepad", DEBUG_EXECUTE_ECHO));
CHECK_RC(ok, EXIT, pControl4->WaitForEvent(0, INFINITE));
CHECK_RC(ok, EXIT, pControl4->Execute(DEBUG_OUTCTL_ALL_CLIENTS, "g", DEBUG_EXECUTE_ECHO));
CHECK_RC(ok, EXIT, pControl4->WaitForEvent(0, INFINITE));
ULONG64 ul64MatchHandle = 0;
CHECK_RC(ok, EXIT, pSymbols3->StartSymbolMatch("*", &ul64MatchHandle));
while (true) {
char aBuffer[1024] = { 0 };
ULONG64 ul64Offset = 0;
CHECK_RC(ok, FINISH, pSymbols3->GetNextSymbolMatch(ul64MatchHandle, aBuffer, sizeof(aBuffer), NULL, &ul64Offset));
printf("%016x: %s\n", ul64Offset, aBuffer);
}
FINISH:
fprintf(stderr, "SUCCESS\n");
EXIT:
pClient5->SetEventCallbacks(NULL);
pControl4->Release();
pClient5->Release();
return 0;
}
int main_exp01(int argc, char** argv) {
PDEBUG_CLIENT5 pClient5 = NULL;
int ok = 0;
CHECK_RC(ok, EXIT, DebugCreate(IID_IDebugClient5, (PVOID*) &pClient5));
CHECK_RC(ok, EXIT, pClient5->StartProcessServerWide(DEBUG_CLASS_USER_WINDOWS, L"tcp:port=11200", NULL));
CHECK_RC(ok, EXIT, pClient5->WaitForProcessServerEnd(INFINITE));
EXIT:
if (pClient5 != NULL) {
pClient5->Release();
}
return 0;
}
#endif
int main_sctldbg(int argc, char** argv) {
if (argc < 1) {
fprintf(stderr, "Something is terribly wrong: argc == 0\n");
}
char* env_java_home = getenv("JAVA_HOME");
if (env_java_home == NULL) {
fprintf(stderr, "JAVA_HOME is not set\n");
fflush(stderr);
return -1;
}
char* java_home = strdup(env_java_home);
size_t home_len = strlen(java_home);
if (java_home[home_len - 1] == '\\') {
java_home[home_len - 1] = '\0';
}
size_t full_len = home_len + sizeof(JDK_JVM_DLL_PATH);
char* full_path = new char[full_len];
HMODULE jvmDll = NULL;
// Try the JRE path first;
strcpy_s(full_path, full_len, java_home);
strcat_s(full_path, full_len, JRE_JVM_DLL_PATH);
fprintf(stderr, "Trying to find jvm.dll at %s\n", full_path);
fflush(stderr);
jvmDll = LoadLibraryA(full_path);
if (jvmDll == NULL) {
// OK, then try the JDK path
strcpy_s(full_path, full_len, java_home);
strcat_s(full_path, full_len, JDK_JVM_DLL_PATH);
fprintf(stderr, "Trying to find jvm.dll at %s\n", full_path);
fflush(stderr);
jvmDll = LoadLibraryA(full_path);
}
free(full_path);
free(java_home);
if (jvmDll == NULL) {
fprintf(stderr, "Could not find the jvm.dll\n");
fflush(stderr);
return -1;
}
fprintf(stderr, "Found it!\n");
fflush(stderr);
#define USE_EXE_AS_JAR
#ifdef USE_EXE_AS_JAR
DWORD fullpath_len = GetFullPathNameA(argv[0], 0, NULL, NULL);
char* fullpath = new char[fullpath_len];
GetFullPathNameA(argv[0], fullpath_len, fullpath, NULL);
size_t cp_opt_len = sizeof(CP_PREFIX) + strlen(fullpath);
char* cp_opt = new char[cp_opt_len];
strcpy_s(cp_opt, cp_opt_len, CP_PREFIX);
strcat_s(cp_opt, cp_opt_len, fullpath);
fflush(stderr);
#endif
JavaVMOption options[2];
JavaVMInitArgs vm_args = { 0 };
vm_args.version = JNI_VERSION_1_8;
vm_args.nOptions = sizeof(options)/sizeof(options[0]);
vm_args.options = options;
options[0].optionString = "-Xrs";
#ifdef USE_EXE_AS_JAR
fprintf(stderr, "Classpath: %s\n", cp_opt);
options[1].optionString = cp_opt;
#else
options[1].optionString = "-Djava.class.path=sctldbgeng.jar";
#endif
//options[2].optionString = "-verbose:class";
vm_args.ignoreUnrecognized = false;
CreateJavaVMFunc create_jvm = NULL;
//create_jvm = JNI_CreateJavaVM;
create_jvm = (CreateJavaVMFunc) GetProcAddress(jvmDll, "JNI_CreateJavaVM");
jint jni_result = create_jvm(&jvm, (void**)&env, &vm_args);
#ifdef USE_EXE_AS_JAR
free(cp_opt);
#endif
if (jni_result != JNI_OK) {
jvm = NULL;
fprintf(stderr, "Could not initialize JVM: %d: ", jni_result);
switch (jni_result) {
case JNI_ERR:
fprintf(stderr, "unknown error");
break;
case JNI_EDETACHED:
fprintf(stderr, "thread detached from the VM");
break;
case JNI_EVERSION:
fprintf(stderr, "JNI version error");
break;
case JNI_ENOMEM:
fprintf(stderr, "not enough memory");
break;
case JNI_EEXIST:
fprintf(stderr, "VM already created");
break;
case JNI_EINVAL:
fprintf(stderr, "invalid arguments");
break;
}
fprintf(stderr, "\n");
fflush(stderr);
return -1;
}
jclass mainCls = env->FindClass(MAIN_CLASS);
if (mainCls == NULL) {
fprintf(stderr, "Could not find main class: %s\n", MAIN_CLASS);
jvm->DestroyJavaVM();
return -1;
}
jmethodID mainMeth = env->GetStaticMethodID(mainCls, "main", "([Ljava/lang/String;)V");
if (mainMeth == NULL) {
fprintf(stderr, "No main(String[] args) method in main class\n");
jvm->DestroyJavaVM();
return -1;
}
jclass stringCls = env->FindClass("java/lang/String");
jobjectArray jargs = env->NewObjectArray(argc - 1, stringCls, NULL);
for (int i = 1; i < argc; i++) {
jstring a = env->NewStringUTF(argv[i]);
if (a == NULL) {
fprintf(stderr, "Could not create Java string for arguments.\n");
jvm->DestroyJavaVM();
return -1;
}
env->SetObjectArrayElement(jargs, i - 1, a);
}
env->CallStaticVoidMethod(mainCls, mainMeth, (jvalue*) jargs);
if (env->ExceptionCheck()) {
env->ExceptionDescribe();
env->ExceptionClear();
}
jvm->DestroyJavaVM();
return 0;
}
int main(int argc, char** argv) {
main_sctldbg(argc, argv);
}
@@ -0,0 +1,109 @@
/* ###
* 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.app.plugin.core.debug.gui.tracermi.launcher;
import java.io.File;
import java.net.SocketAddress;
import java.util.*;
import javax.swing.Icon;
import ghidra.app.plugin.core.debug.gui.tracermi.launcher.ScriptAttributesParser.ScriptAttributes;
import ghidra.dbg.target.TargetMethod.ParameterDescription;
import ghidra.debug.api.tracermi.TerminalSession;
import ghidra.program.model.listing.Program;
import ghidra.util.HelpLocation;
import ghidra.util.task.TaskMonitor;
public abstract class AbstractScriptTraceRmiLaunchOffer extends AbstractTraceRmiLaunchOffer {
protected final File script;
protected final String configName;
protected final ScriptAttributes attrs;
public AbstractScriptTraceRmiLaunchOffer(TraceRmiLauncherServicePlugin plugin, Program program,
File script, String configName, ScriptAttributes attrs) {
super(plugin, program);
this.script = script;
this.configName = configName;
this.attrs = attrs;
}
@Override
public String getConfigName() {
return configName;
}
@Override
public String getTitle() {
return attrs.title();
}
@Override
public String getDescription() {
return attrs.description();
}
@Override
public List<String> getMenuPath() {
return attrs.menuPath();
}
@Override
public String getMenuGroup() {
return attrs.menuGroup();
}
@Override
public String getMenuOrder() {
return attrs.menuOrder();
}
@Override
public Icon getIcon() {
return attrs.icon();
}
@Override
public HelpLocation getHelpLocation() {
return attrs.helpLocation();
}
@Override
public Map<String, ParameterDescription<?>> getParameters() {
return attrs.parameters();
}
protected abstract void prepareSubprocess(List<String> commandLine, Map<String, String> env,
Map<String, ?> args, SocketAddress address);
@Override
protected void launchBackEnd(TaskMonitor monitor, Map<String, TerminalSession> sessions,
Map<String, ?> args, SocketAddress address) throws Exception {
List<String> commandLine = new ArrayList<>();
Map<String, String> env = new HashMap<>(System.getenv());
prepareSubprocess(commandLine, env, args, address);
for (String tty : attrs.extraTtys()) {
NullPtyTerminalSession ns = nullPtyTerminal();
env.put(tty, ns.name());
sessions.put(ns.name(), ns);
}
sessions.put("Shell",
runInTerminal(commandLine, env, script.getParentFile(), sessions.values()));
}
}
@@ -420,8 +420,7 @@ public abstract class AbstractTraceRmiLaunchOffer implements TraceRmiLaunchOffer
} }
protected PtyTerminalSession runInTerminal(List<String> commandLine, Map<String, String> env, protected PtyTerminalSession runInTerminal(List<String> commandLine, Map<String, String> env,
Collection<TerminalSession> subordinates) File workingDirectory, Collection<TerminalSession> subordinates) throws IOException {
throws IOException {
PtyFactory factory = getPtyFactory(); PtyFactory factory = getPtyFactory();
Pty pty = factory.openpty(); Pty pty = factory.openpty();
@@ -432,13 +431,19 @@ public abstract class AbstractTraceRmiLaunchOffer implements TraceRmiLaunchOffer
TerminalListener resizeListener = new TerminalListener() { TerminalListener resizeListener = new TerminalListener() {
@Override @Override
public void resized(short cols, short rows) { public void resized(short cols, short rows) {
parent.setWindowSize(cols, rows); try {
parent.setWindowSize(cols, rows);
}
catch (Exception e) {
Msg.error(this, "Could not resize pty: " + e);
}
} }
}; };
terminal.addTerminalListener(resizeListener); terminal.addTerminalListener(resizeListener);
env.put("TERM", "xterm-256color"); env.put("TERM", "xterm-256color");
PtySession session = pty.getChild().session(commandLine.toArray(String[]::new), env); PtySession session =
pty.getChild().session(commandLine.toArray(String[]::new), env, workingDirectory);
Thread waiter = new Thread(() -> { Thread waiter = new Thread(() -> {
try { try {
@@ -0,0 +1,60 @@
/* ###
* 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.app.plugin.core.debug.gui.tracermi.launcher;
import java.util.stream.Stream;
import generic.jar.ResourceFile;
import ghidra.app.plugin.core.debug.DebuggerPluginPackage;
import ghidra.debug.spi.tracermi.TraceRmiLaunchOpinion;
import ghidra.framework.Application;
import ghidra.framework.options.OptionType;
import ghidra.framework.options.Options;
import ghidra.framework.plugintool.PluginTool;
import ghidra.framework.plugintool.util.PluginUtils;
import ghidra.util.HelpLocation;
public abstract class AbstractTraceRmiLaunchOpinion implements TraceRmiLaunchOpinion {
@Override
public void registerOptions(Options options) {
String pluginName = PluginUtils.getPluginNameFromClass(TraceRmiLauncherServicePlugin.class);
options.registerOption(TraceRmiLauncherServicePlugin.OPTION_NAME_SCRIPT_PATHS,
OptionType.STRING_TYPE, "", new HelpLocation(pluginName, "options"),
"Paths to search for user-created debugger launchers", new ScriptPathsPropertyEditor());
}
@Override
public boolean requiresRefresh(String optionName) {
return TraceRmiLauncherServicePlugin.OPTION_NAME_SCRIPT_PATHS.equals(optionName);
}
protected Stream<ResourceFile> getModuleScriptPaths() {
return Application.findModuleSubDirectories("data/debugger-launchers").stream();
}
protected Stream<ResourceFile> getUserScriptPaths(PluginTool tool) {
Options options = tool.getOptions(DebuggerPluginPackage.NAME);
String scriptPaths =
options.getString(TraceRmiLauncherServicePlugin.OPTION_NAME_SCRIPT_PATHS, "");
return scriptPaths.lines().filter(d -> !d.isBlank()).map(ResourceFile::new);
}
protected Stream<ResourceFile> getScriptPaths(PluginTool tool) {
return Stream.concat(getModuleScriptPaths(), getUserScriptPaths(tool));
}
}
@@ -0,0 +1,70 @@
/* ###
* 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.app.plugin.core.debug.gui.tracermi.launcher;
import java.io.File;
import java.io.FileNotFoundException;
import java.net.SocketAddress;
import java.util.List;
import java.util.Map;
import ghidra.app.plugin.core.debug.gui.tracermi.launcher.ScriptAttributesParser.ScriptAttributes;
import ghidra.program.model.listing.Program;
/**
* A launcher implemented by a simple DOS/Windows batch file.
*
* <p>
* The script must start with an attributes header in a comment block.
*/
public class BatchScriptTraceRmiLaunchOffer extends AbstractScriptTraceRmiLaunchOffer {
public static final String REM = "::";
public static final int REM_LEN = REM.length();
public static BatchScriptTraceRmiLaunchOffer create(TraceRmiLauncherServicePlugin plugin,
Program program, File script) throws FileNotFoundException {
ScriptAttributesParser parser = new ScriptAttributesParser() {
@Override
protected boolean ignoreLine(int lineNo, String line) {
return line.isBlank();
}
@Override
protected String removeDelimiter(String line) {
String stripped = line.stripLeading();
if (!stripped.startsWith(REM)) {
return null;
}
return stripped.substring(REM_LEN);
}
};
ScriptAttributes attrs = parser.parseFile(script);
return new BatchScriptTraceRmiLaunchOffer(plugin, program, script,
"BATCH_FILE:" + script.getName(), attrs);
}
private BatchScriptTraceRmiLaunchOffer(TraceRmiLauncherServicePlugin plugin, Program program,
File script, String configName, ScriptAttributes attrs) {
super(plugin, program, script, configName, attrs);
}
@Override
protected void prepareSubprocess(List<String> commandLine, Map<String, String> env,
Map<String, ?> args, SocketAddress address) {
ScriptAttributesParser.processArguments(commandLine, env, script, attrs.parameters(), args,
address);
}
}
@@ -0,0 +1,49 @@
/* ###
* 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.app.plugin.core.debug.gui.tracermi.launcher;
import java.util.Collection;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import generic.jar.ResourceFile;
import ghidra.debug.api.tracermi.TraceRmiLaunchOffer;
import ghidra.program.model.listing.Program;
import ghidra.util.Msg;
public class BatchScriptTraceRmiLaunchOpinion extends AbstractTraceRmiLaunchOpinion {
@Override
public Collection<TraceRmiLaunchOffer> getOffers(TraceRmiLauncherServicePlugin plugin,
Program program) {
return getScriptPaths(plugin.getTool())
.flatMap(rf -> Stream.of(rf.listFiles(crf -> crf.getName().endsWith(".bat"))))
.flatMap(sf -> createOffer(plugin, program, sf))
.collect(Collectors.toList());
}
protected Stream<TraceRmiLaunchOffer> createOffer(TraceRmiLauncherServicePlugin plugin,
Program program, ResourceFile scriptFile) {
try {
return Stream.of(
BatchScriptTraceRmiLaunchOffer.create(plugin, program, scriptFile.getFile(false)));
}
catch (Exception e) {
Msg.error(this, "Could not offer " + scriptFile + ": " + e.getMessage(), e);
return Stream.of();
}
}
}
@@ -88,6 +88,9 @@ public class LaunchAction extends MultiActionDockingAction {
ConfigLast findMostRecentConfig() { ConfigLast findMostRecentConfig() {
Program program = plugin.currentProgram; Program program = plugin.currentProgram;
if (program == null) {
return null;
}
ConfigLast best = null; ConfigLast best = null;
ProgramUserData userData = program.getProgramUserData(); ProgramUserData userData = program.getProgramUserData();
@@ -113,14 +116,16 @@ public class LaunchAction extends MultiActionDockingAction {
List<DockingActionIf> actions = new ArrayList<>(); List<DockingActionIf> actions = new ArrayList<>();
ProgramUserData userData = program.getProgramUserData();
Map<String, Long> saved = new HashMap<>(); Map<String, Long> saved = new HashMap<>();
for (String propName : userData.getStringPropertyNames()) { if (program != null) {
ConfigLast check = checkSavedConfig(userData, propName); ProgramUserData userData = program.getProgramUserData();
if (check == null) { for (String propName : userData.getStringPropertyNames()) {
continue; ConfigLast check = checkSavedConfig(userData, propName);
if (check == null) {
continue;
}
saved.put(check.configName, check.last);
} }
saved.put(check.configName, check.last);
} }
for (TraceRmiLaunchOffer offer : offers) { for (TraceRmiLaunchOffer offer : offers) {
@@ -134,6 +139,8 @@ public class LaunchAction extends MultiActionDockingAction {
.build()); .build());
Long last = saved.get(offer.getConfigName()); Long last = saved.get(offer.getConfigName());
if (last == null) { if (last == null) {
// NB. If program == null, this will always happen.
// Thus, no worries about program.getName() below.
continue; continue;
} }
actions.add(new ActionBuilder(offer.getConfigName(), plugin.getName()) actions.add(new ActionBuilder(offer.getConfigName(), plugin.getName())
@@ -172,6 +179,11 @@ public class LaunchAction extends MultiActionDockingAction {
// Make accessible to this file // Make accessible to this file
return super.showPopup(); return super.showPopup();
} }
@Override
public String getToolTipText() {
return getDescription();
}
} }
@Override @Override
@@ -180,19 +192,45 @@ public class LaunchAction extends MultiActionDockingAction {
} }
@Override @Override
public void actionPerformed(ActionContext context) { public boolean isEnabledForContext(ActionContext context) {
// See comment on super method about use of runLater return plugin.currentProgram != null;
ConfigLast last = findMostRecentConfig(); }
protected TraceRmiLaunchOffer findOffer(ConfigLast last) {
if (last == null) { if (last == null) {
Swing.runLater(() -> button.showPopup()); return null;
return;
} }
for (TraceRmiLaunchOffer offer : plugin.getOffers(plugin.currentProgram)) { for (TraceRmiLaunchOffer offer : plugin.getOffers(plugin.currentProgram)) {
if (offer.getConfigName().equals(last.configName)) { if (offer.getConfigName().equals(last.configName)) {
plugin.relaunch(offer); return offer;
return;
} }
} }
Swing.runLater(() -> button.showPopup()); return null;
}
@Override
public void actionPerformed(ActionContext context) {
// See comment on super method about use of runLater
ConfigLast last = findMostRecentConfig();
TraceRmiLaunchOffer offer = findOffer(last);
if (offer == null) {
Swing.runLater(() -> button.showPopup());
return;
}
plugin.relaunch(offer);
}
@Override
public String getDescription() {
Program program = plugin.currentProgram;
if (program == null) {
return "Launch (program required)";
}
ConfigLast last = findMostRecentConfig();
TraceRmiLaunchOffer offer = findOffer(last);
if (last == null) {
return "Configure and launch " + program.getName();
}
return "Re-launch " + program.getName() + " using " + offer.getTitle();
} }
} }
@@ -20,63 +20,30 @@ import java.util.stream.Collectors;
import java.util.stream.Stream; import java.util.stream.Stream;
import generic.jar.ResourceFile; import generic.jar.ResourceFile;
import ghidra.app.plugin.core.debug.DebuggerPluginPackage;
import ghidra.debug.api.tracermi.TraceRmiLaunchOffer; import ghidra.debug.api.tracermi.TraceRmiLaunchOffer;
import ghidra.debug.spi.tracermi.TraceRmiLaunchOpinion;
import ghidra.framework.Application;
import ghidra.framework.options.OptionType;
import ghidra.framework.options.Options;
import ghidra.framework.plugintool.PluginTool;
import ghidra.framework.plugintool.util.PluginUtils;
import ghidra.program.model.listing.Program; import ghidra.program.model.listing.Program;
import ghidra.util.HelpLocation;
import ghidra.util.Msg; import ghidra.util.Msg;
public class UnixShellScriptTraceRmiLaunchOpinion implements TraceRmiLaunchOpinion { public class UnixShellScriptTraceRmiLaunchOpinion extends AbstractTraceRmiLaunchOpinion {
@Override
public void registerOptions(Options options) {
String pluginName = PluginUtils.getPluginNameFromClass(TraceRmiLauncherServicePlugin.class);
options.registerOption(TraceRmiLauncherServicePlugin.OPTION_NAME_SCRIPT_PATHS,
OptionType.STRING_TYPE, "", new HelpLocation(pluginName, "options"),
"Paths to search for user-created debugger launchers", new ScriptPathsPropertyEditor());
}
@Override
public boolean requiresRefresh(String optionName) {
return TraceRmiLauncherServicePlugin.OPTION_NAME_SCRIPT_PATHS.equals(optionName);
}
protected Stream<ResourceFile> getModuleScriptPaths() {
return Application.findModuleSubDirectories("data/debugger-launchers").stream();
}
protected Stream<ResourceFile> getUserScriptPaths(PluginTool tool) {
Options options = tool.getOptions(DebuggerPluginPackage.NAME);
String scriptPaths =
options.getString(TraceRmiLauncherServicePlugin.OPTION_NAME_SCRIPT_PATHS, "");
return scriptPaths.lines().filter(d -> !d.isBlank()).map(ResourceFile::new);
}
protected Stream<ResourceFile> getScriptPaths(PluginTool tool) {
return Stream.concat(getModuleScriptPaths(), getUserScriptPaths(tool));
}
@Override @Override
public Collection<TraceRmiLaunchOffer> getOffers(TraceRmiLauncherServicePlugin plugin, public Collection<TraceRmiLaunchOffer> getOffers(TraceRmiLauncherServicePlugin plugin,
Program program) { Program program) {
return getScriptPaths(plugin.getTool()) return getScriptPaths(plugin.getTool())
.flatMap(rf -> Stream.of(rf.listFiles(crf -> crf.getName().endsWith(".sh")))) .flatMap(rf -> Stream.of(rf.listFiles(crf -> crf.getName().endsWith(".sh"))))
.flatMap(sf -> { .flatMap(sf -> createOffer(plugin, program, sf))
try {
return Stream.of(UnixShellScriptTraceRmiLaunchOffer.create(plugin, program,
sf.getFile(false)));
}
catch (Exception e) {
Msg.error(this, "Could not offer " + sf + ":" + e.getMessage(), e);
return Stream.of();
}
})
.collect(Collectors.toList()); .collect(Collectors.toList());
} }
protected Stream<TraceRmiLaunchOffer> createOffer(TraceRmiLauncherServicePlugin plugin,
Program program, ResourceFile scriptFile) {
try {
return Stream.of(UnixShellScriptTraceRmiLaunchOffer.create(plugin, program,
scriptFile.getFile(false)));
}
catch (Exception e) {
Msg.error(this, "Could not offer " + scriptFile + ": " + e.getMessage(), e);
return Stream.of();
}
}
} }
@@ -20,8 +20,6 @@ import java.math.BigInteger;
import java.net.Socket; import java.net.Socket;
import java.net.SocketAddress; import java.net.SocketAddress;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.Instant; import java.time.Instant;
import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatter;
import java.util.*; import java.util.*;
@@ -329,17 +327,17 @@ public class TraceRmiHandler implements TraceRmiConnection {
} }
} }
protected DomainFolder createFolders(DomainFolder parent, Path path) protected DomainFolder createFolders(DomainFolder parent, List<String> path)
throws InvalidNameException, IOException { throws InvalidNameException, IOException {
return createFolders(parent, path, 0); return createFolders(parent, path, 0);
} }
protected DomainFolder createFolders(DomainFolder parent, Path path, int index) protected DomainFolder createFolders(DomainFolder parent, List<String> path, int index)
throws InvalidNameException, IOException { throws InvalidNameException, IOException {
if (path == null && index == 0 || index == path.getNameCount()) { if (path == null && index == 0 || index == path.size()) {
return parent; return parent;
} }
String name = path.getName(index).toString(); String name = path.get(index);
return createFolders(getOrCreateFolder(parent, name), path, index + 1); return createFolders(getOrCreateFolder(parent, name), path, index + 1);
} }
@@ -859,10 +857,10 @@ public class TraceRmiHandler implements TraceRmiConnection {
protected ReplyCreateTrace handleCreateTrace(RequestCreateTrace req) protected ReplyCreateTrace handleCreateTrace(RequestCreateTrace req)
throws InvalidNameException, IOException, CancelledException { throws InvalidNameException, IOException, CancelledException {
DomainFolder traces = getOrCreateNewTracesFolder(); DomainFolder traces = getOrCreateNewTracesFolder();
Path path = Paths.get(req.getPath().getPath()); List<String> path = sanitizePath(req.getPath().getPath());
DomainFolder folder = createFolders(traces, path.getParent()); DomainFolder folder = createFolders(traces, path.subList(0, path.size() - 1));
CompilerSpec cs = requireCompilerSpec(req.getLanguage(), req.getCompiler()); CompilerSpec cs = requireCompilerSpec(req.getLanguage(), req.getCompiler());
DBTrace trace = new DBTrace(path.getFileName().toString(), cs, this); DBTrace trace = new DBTrace(path.get(path.size() - 1), cs, this);
TraceRmiTarget target = new TraceRmiTarget(plugin.getTool(), this, trace); TraceRmiTarget target = new TraceRmiTarget(plugin.getTool(), this, trace);
DoId doId = requireAvailableDoId(req.getOid()); DoId doId = requireAvailableDoId(req.getOid());
openTraces.put(new OpenTrace(doId, trace, target)); openTraces.put(new OpenTrace(doId, trace, target));
@@ -870,6 +868,10 @@ public class TraceRmiHandler implements TraceRmiConnection {
return ReplyCreateTrace.getDefaultInstance(); return ReplyCreateTrace.getDefaultInstance();
} }
protected static List<String> sanitizePath(String path) {
return Stream.of(path.split("\\\\|/")).filter(p -> !p.isBlank()).toList();
}
protected ReplyDeleteBytes handleDeleteBytes(RequestDeleteBytes req) protected ReplyDeleteBytes handleDeleteBytes(RequestDeleteBytes req)
throws AddressOverflowException { throws AddressOverflowException {
OpenTrace open = requireOpenTrace(req.getOid()); OpenTrace open = requireOpenTrace(req.getOid());
@@ -251,7 +251,7 @@ public class DebuggerModulesProvider extends ComponentProviderAdapter {
if (context instanceof DebuggerObjectActionContext ctx) { if (context instanceof DebuggerObjectActionContext ctx) {
return DebuggerModulesPanel.getSelectedModulesFromContext(ctx); return DebuggerModulesPanel.getSelectedModulesFromContext(ctx);
} }
return null; return Set.of();
} }
protected static Set<TraceSection> getSelectedSections(ActionContext context) { protected static Set<TraceSection> getSelectedSections(ActionContext context) {
@@ -264,7 +264,7 @@ public class DebuggerModulesProvider extends ComponentProviderAdapter {
if (context instanceof DebuggerObjectActionContext ctx) { if (context instanceof DebuggerObjectActionContext ctx) {
return DebuggerModulesPanel.getSelectedSectionsFromContext(ctx); return DebuggerModulesPanel.getSelectedSectionsFromContext(ctx);
} }
return null; return Set.of();
} }
protected static AddressSetView getSelectedAddresses(ActionContext context) { protected static AddressSetView getSelectedAddresses(ActionContext context) {
@@ -299,7 +299,7 @@ public class DebuggerModulesProvider extends ComponentProviderAdapter {
} }
AddressSetView sel = getSelectedAddresses(context); AddressSetView sel = getSelectedAddresses(context);
if (sel == null) { if (sel == null || sel.isEmpty()) {
return; return;
} }
@@ -540,9 +540,8 @@ public class DebuggerModulesProvider extends ComponentProviderAdapter {
.onAction(this::activatedMapModules) .onAction(this::activatedMapModules)
.buildAndInstallLocal(this); .buildAndInstallLocal(this);
actionMapModuleTo = MapModuleToAction.builder(plugin) actionMapModuleTo = MapModuleToAction.builder(plugin)
.withContext(DebuggerModuleActionContext.class) .enabledWhen(ctx -> currentProgram != null && getSelectedModules(ctx).size() == 1)
.enabledWhen(ctx -> currentProgram != null && ctx.getSelectedModules().size() == 1) .popupWhen(ctx -> currentProgram != null && getSelectedModules(ctx).size() == 1)
.popupWhen(ctx -> currentProgram != null && ctx.getSelectedModules().size() == 1)
.onAction(this::activatedMapModuleTo) .onAction(this::activatedMapModuleTo)
.buildAndInstallLocal(this); .buildAndInstallLocal(this);
actionMapSections = MapSectionsAction.builder(plugin) actionMapSections = MapSectionsAction.builder(plugin)
@@ -551,9 +550,8 @@ public class DebuggerModulesProvider extends ComponentProviderAdapter {
.onAction(this::activatedMapSections) .onAction(this::activatedMapSections)
.buildAndInstallLocal(this); .buildAndInstallLocal(this);
actionMapSectionTo = MapSectionToAction.builder(plugin) actionMapSectionTo = MapSectionToAction.builder(plugin)
.withContext(DebuggerSectionActionContext.class) .enabledWhen(ctx -> currentProgram != null && getSelectedSections(ctx).size() == 1)
.enabledWhen(ctx -> currentProgram != null && ctx.getSelectedSections().size() == 1) .popupWhen(ctx -> currentProgram != null && getSelectedSections(ctx).size() == 1)
.popupWhen(ctx -> currentProgram != null && ctx.getSelectedSections().size() == 1)
.onAction(this::activatedMapSectionTo) .onAction(this::activatedMapSectionTo)
.buildAndInstallLocal(this); .buildAndInstallLocal(this);
actionMapSectionsTo = MapSectionsToAction.builder(plugin) actionMapSectionsTo = MapSectionsToAction.builder(plugin)
@@ -25,6 +25,7 @@ import ghidra.program.model.listing.Program;
import ghidra.program.model.mem.MemoryBlock; import ghidra.program.model.mem.MemoryBlock;
import ghidra.trace.model.Lifespan; import ghidra.trace.model.Lifespan;
import ghidra.trace.model.memory.TraceMemoryRegion; import ghidra.trace.model.memory.TraceMemoryRegion;
import ghidra.trace.model.memory.TraceObjectMemoryRegion;
import ghidra.trace.model.modules.TraceModule; import ghidra.trace.model.modules.TraceModule;
public class DefaultModuleMapProposal public class DefaultModuleMapProposal
@@ -207,10 +208,14 @@ public class DefaultModuleMapProposal
catch (AddressOverflowException e) { catch (AddressOverflowException e) {
return; // Just score it as having no matches? return; // Just score it as having no matches?
} }
Lifespan lifespan = module.getLifespan();
for (TraceMemoryRegion region : module.getTrace() for (TraceMemoryRegion region : module.getTrace()
.getMemoryManager() .getMemoryManager()
.getRegionsIntersecting(module.getLifespan(), moduleRange)) { .getRegionsIntersecting(lifespan, moduleRange)) {
getMatcher(region.getMinAddress().subtract(moduleBase)).region = region; Address min = region instanceof TraceObjectMemoryRegion objReg
? objReg.getMinAddress(lifespan.lmin())
: region.getMinAddress();
getMatcher(min.subtract(moduleBase)).region = region;
} }
} }
@@ -15,8 +15,8 @@
*/ */
package ghidra.dbg.util; package ghidra.dbg.util;
import java.util.ArrayList; import java.util.*;
import java.util.List; import java.util.stream.Collectors;
public class ShellUtils { public class ShellUtils {
enum State { enum State {
@@ -139,4 +139,11 @@ public class ShellUtils {
} }
return line.toString(); return line.toString();
} }
public static String generateEnvBlock(Map<String, String> env) {
return env.entrySet()
.stream()
.map(e -> e.getKey() + "=" + e.getValue() + "\0")
.collect(Collectors.joining()); // NB. JNA adds final terminator
}
} }
@@ -195,14 +195,22 @@ public class DBTraceObjectMemoryRegion implements TraceObjectMemoryRegion, DBTra
} }
} }
@Override
public AddressRange getRange(long snap) {
try (LockHold hold = object.getTrace().lockRead()) {
// TODO: Caching without regard to snap seems bad
return range = TraceObjectInterfaceUtils.getValue(object, snap,
TargetMemoryRegion.RANGE_ATTRIBUTE_NAME, AddressRange.class, range);
}
}
@Override @Override
public AddressRange getRange() { public AddressRange getRange() {
try (LockHold hold = object.getTrace().lockRead()) { try (LockHold hold = object.getTrace().lockRead()) {
if (object.getLife().isEmpty()) { if (object.getLife().isEmpty()) {
return range; return range;
} }
return range = TraceObjectInterfaceUtils.getValue(object, getCreationSnap(), return getRange(getCreationSnap());
TargetMemoryRegion.RANGE_ATTRIBUTE_NAME, AddressRange.class, range);
} }
} }
@@ -213,6 +221,12 @@ public class DBTraceObjectMemoryRegion implements TraceObjectMemoryRegion, DBTra
} }
} }
@Override
public Address getMinAddress(long snap) {
AddressRange range = getRange(snap);
return range == null ? null : range.getMinAddress();
}
@Override @Override
public Address getMinAddress() { public Address getMinAddress() {
AddressRange range = getRange(); AddressRange range = getRange();
@@ -226,6 +240,12 @@ public class DBTraceObjectMemoryRegion implements TraceObjectMemoryRegion, DBTra
} }
} }
@Override
public Address getMaxAddress(long snap) {
AddressRange range = getRange(snap);
return range == null ? null : range.getMaxAddress();
}
@Override @Override
public Address getMaxAddress() { public Address getMaxAddress() {
AddressRange range = getRange(); AddressRange range = getRange();
@@ -20,6 +20,7 @@ import java.util.Set;
import ghidra.dbg.target.TargetMemoryRegion; import ghidra.dbg.target.TargetMemoryRegion;
import ghidra.dbg.target.TargetObject; import ghidra.dbg.target.TargetObject;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressRange; import ghidra.program.model.address.AddressRange;
import ghidra.trace.model.Lifespan; import ghidra.trace.model.Lifespan;
import ghidra.trace.model.target.TraceObjectInterface; import ghidra.trace.model.target.TraceObjectInterface;
@@ -39,6 +40,12 @@ public interface TraceObjectMemoryRegion extends TraceMemoryRegion, TraceObjectI
void setRange(Lifespan lifespan, AddressRange range); void setRange(Lifespan lifespan, AddressRange range);
AddressRange getRange(long snap);
Address getMinAddress(long snap);
Address getMaxAddress(long snap);
void setFlags(Lifespan lifespan, Collection<TraceMemoryFlag> flags); void setFlags(Lifespan lifespan, Collection<TraceMemoryFlag> flags);
void addFlags(Lifespan lifespan, Collection<TraceMemoryFlag> flags); void addFlags(Lifespan lifespan, Collection<TraceMemoryFlag> flags);
@@ -15,6 +15,7 @@
*/ */
package ghidra.pty; package ghidra.pty;
import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.util.*; import java.util.*;
@@ -51,19 +52,28 @@ public interface PtyChild extends PtyEndpoint {
* *
* @param args the image path and arguments * @param args the image path and arguments
* @param env the environment * @param env the environment
* @param workingDirectory the working directory
* @param mode the terminal mode. If a mode is not implemented, it may be silently ignored. * @param mode the terminal mode. If a mode is not implemented, it may be silently ignored.
* @return a handle to the subprocess * @return a handle to the subprocess
* @throws IOException if the session could not be started * @throws IOException if the session could not be started
*/ */
PtySession session(String[] args, Map<String, String> env, Collection<TermMode> mode) PtySession session(String[] args, Map<String, String> env, File workingDirectory,
throws IOException; Collection<TermMode> mode) throws IOException;
/** /**
* @see #session(String[], Map, Collection) * @see #session(String[], Map, File, Collection)
*/
default PtySession session(String[] args, Map<String, String> env, File workingDirectory,
TermMode... mode) throws IOException {
return session(args, env, workingDirectory, List.of(mode));
}
/**
* @see #session(String[], Map, File, Collection)
*/ */
default PtySession session(String[] args, Map<String, String> env, TermMode... mode) default PtySession session(String[] args, Map<String, String> env, TermMode... mode)
throws IOException { throws IOException {
return session(args, env, List.of(mode)); return session(args, env, null, List.of(mode));
} }
/** /**
@@ -57,13 +57,13 @@ public class LinuxPtyChild extends LinuxPtyEndpoint implements PtyChild {
* program is active before sending special characters. * program is active before sending special characters.
*/ */
@Override @Override
public PtySession session(String[] args, Map<String, String> env, Collection<TermMode> mode) public PtySession session(String[] args, Map<String, String> env, File workingDirectory,
throws IOException { Collection<TermMode> mode) throws IOException {
return sessionUsingJavaLeader(args, env, mode); return sessionUsingJavaLeader(args, env, workingDirectory, mode);
} }
protected PtySession sessionUsingJavaLeader(String[] args, Map<String, String> env, protected PtySession sessionUsingJavaLeader(String[] args, Map<String, String> env,
Collection<TermMode> mode) throws IOException { File workingDirectory, Collection<TermMode> mode) throws IOException {
final List<String> argsList = new ArrayList<>(); final List<String> argsList = new ArrayList<>();
String javaCommand = String javaCommand =
System.getProperty("java.home") + File.separator + "bin" + File.separator + "java"; System.getProperty("java.home") + File.separator + "bin" + File.separator + "java";
@@ -78,6 +78,9 @@ public class LinuxPtyChild extends LinuxPtyEndpoint implements PtyChild {
if (env != null) { if (env != null) {
builder.environment().putAll(env); builder.environment().putAll(env);
} }
if (workingDirectory != null) {
builder.directory(workingDirectory);
}
builder.inheritIO(); builder.inheritIO();
applyMode(mode); applyMode(mode);
@@ -48,8 +48,11 @@ public class SshPtyChild extends SshPtyEndpoint implements PtyChild {
} }
@Override @Override
public SshPtySession session(String[] args, Map<String, String> env, Collection<TermMode> mode) public SshPtySession session(String[] args, Map<String, String> env, File workingDirectory,
throws IOException { Collection<TermMode> mode) throws IOException {
if (workingDirectory != null) {
throw new UnsupportedOperationException();
}
/** /**
* TODO: This syntax assumes a UNIX-style shell, and even among them, this may not be * TODO: This syntax assumes a UNIX-style shell, and even among them, this may not be
* universal. This certainly works for my version of bash :) * universal. This certainly works for my version of bash :)
@@ -15,6 +15,7 @@
*/ */
package ghidra.pty.windows; package ghidra.pty.windows;
import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.util.*; import java.util.*;
@@ -75,7 +76,7 @@ public class ConPtyChild extends ConPtyEndpoint implements PtyChild {
@Override @Override
public LocalWindowsNativeProcessPtySession session(String[] args, Map<String, String> env, public LocalWindowsNativeProcessPtySession session(String[] args, Map<String, String> env,
Collection<TermMode> mode) throws IOException { File workingDirectory, Collection<TermMode> mode) throws IOException {
/** /**
* TODO: How to incorporate environment into CreateProcess? * TODO: How to incorporate environment into CreateProcess?
* *
@@ -91,9 +92,11 @@ public class ConPtyChild extends ConPtyEndpoint implements PtyChild {
null /*lpProcessAttributes*/, null /*lpProcessAttributes*/,
null /*lpThreadAttributes*/, null /*lpThreadAttributes*/,
false /*bInheritHandles*/, false /*bInheritHandles*/,
ConPty.EXTENDED_STARTUPINFO_PRESENT /*dwCreationFlags*/, new DWORD(Kernel32.EXTENDED_STARTUPINFO_PRESENT |
null /*lpEnvironment*/, Kernel32.CREATE_UNICODE_ENVIRONMENT) /*dwCreationFlags*/,
null /*lpCurrentDirectory*/, env == null ? null : new WString(ShellUtils.generateEnvBlock(env)),
workingDirectory == null ? null
: new WString(workingDirectory.getAbsolutePath()) /*lpCurrentDirectory*/,
si /*lpStartupInfo*/, si /*lpStartupInfo*/,
pi /*lpProcessInformation*/).booleanValue()) { pi /*lpProcessInformation*/).booleanValue()) {
throw new LastErrorException(Kernel32.INSTANCE.GetLastError()); throw new LastErrorException(Kernel32.INSTANCE.GetLastError());
@@ -83,7 +83,9 @@ public class HandleInputStream extends InputStream {
public synchronized int read(byte[] b, int off, int len) throws IOException { public synchronized int read(byte[] b, int off, int len) throws IOException {
byte[] temp = new byte[len]; byte[] temp = new byte[len];
int read = read(temp); int read = read(temp);
System.arraycopy(temp, 0, b, off, read); if (read > 0) {
System.arraycopy(temp, 0, b, off, read);
}
return read; return read;
} }
@@ -57,7 +57,7 @@ public interface ConsoleApiNative extends StdCallLibrary {
WinBase.SECURITY_ATTRIBUTES lpThreadAttributes, WinBase.SECURITY_ATTRIBUTES lpThreadAttributes,
boolean bInheritHandles, boolean bInheritHandles,
DWORD dwCreationFlags, DWORD dwCreationFlags,
Pointer lpEnvironment, WString lpEnvironment,
WString lpCurrentDirectory, WString lpCurrentDirectory,
STARTUPINFOEX lpStartupInfo, STARTUPINFOEX lpStartupInfo,
WinBase.PROCESS_INFORMATION lpProcessInformation); WinBase.PROCESS_INFORMATION lpProcessInformation);
@@ -17,6 +17,7 @@ package ghidra.app.plugin.core.debug.gui.tracermi.launcher;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertFalse;
import static org.junit.Assume.assumeTrue;
import java.util.*; import java.util.*;
@@ -28,6 +29,7 @@ import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerTest;
import ghidra.app.services.TraceRmiLauncherService; import ghidra.app.services.TraceRmiLauncherService;
import ghidra.debug.api.tracermi.TraceRmiLaunchOffer; import ghidra.debug.api.tracermi.TraceRmiLaunchOffer;
import ghidra.debug.api.tracermi.TraceRmiLaunchOffer.*; import ghidra.debug.api.tracermi.TraceRmiLaunchOffer.*;
import ghidra.framework.OperatingSystem;
import ghidra.util.task.ConsoleTaskMonitor; import ghidra.util.task.ConsoleTaskMonitor;
public class TraceRmiLauncherServicePluginTest extends AbstractGhidraHeadedDebuggerTest { public class TraceRmiLauncherServicePluginTest extends AbstractGhidraHeadedDebuggerTest {
@@ -50,7 +52,7 @@ public class TraceRmiLauncherServicePluginTest extends AbstractGhidraHeadedDebug
assertFalse(launcherService.getOffers(program).isEmpty()); assertFalse(launcherService.getOffers(program).isEmpty());
} }
protected LaunchConfigurator fileOnly(String file) { protected LaunchConfigurator gdbFileOnly(String file) {
return new LaunchConfigurator() { return new LaunchConfigurator() {
@Override @Override
public Map<String, ?> configureLauncher(TraceRmiLaunchOffer offer, public Map<String, ?> configureLauncher(TraceRmiLaunchOffer offer,
@@ -65,16 +67,18 @@ public class TraceRmiLauncherServicePluginTest extends AbstractGhidraHeadedDebug
// @Test // This is currently hanging the test machine. The gdb process is left running // @Test // This is currently hanging the test machine. The gdb process is left running
public void testLaunchLocalGdb() throws Exception { public void testLaunchLocalGdb() throws Exception {
assumeTrue(OperatingSystem.CURRENT_OPERATING_SYSTEM == OperatingSystem.LINUX);
createProgram(getSLEIGH_X86_64_LANGUAGE()); createProgram(getSLEIGH_X86_64_LANGUAGE());
try (Transaction tx = program.openTransaction("Rename")) { try (Transaction tx = program.openTransaction("Rename")) {
program.setName("bash"); program.setName("bash");
} }
programManager.openProgram(program); programManager.openProgram(program);
TraceRmiLaunchOffer gdbOffer = findByTitle(launcherService.getOffers(program), "gdb"); TraceRmiLaunchOffer offer = findByTitle(launcherService.getOffers(program), "gdb");
try (LaunchResult result = try (LaunchResult result =
gdbOffer.launchProgram(new ConsoleTaskMonitor(), fileOnly("/usr/bin/bash"))) { offer.launchProgram(new ConsoleTaskMonitor(), gdbFileOnly("/usr/bin/bash"))) {
if (result.exception() != null) { if (result.exception() != null) {
throw new AssertionError(result.exception()); throw new AssertionError(result.exception());
} }
@@ -83,4 +87,39 @@ public class TraceRmiLauncherServicePluginTest extends AbstractGhidraHeadedDebug
assertEquals(getSLEIGH_X86_64_LANGUAGE(), result.trace().getBaseLanguage()); assertEquals(getSLEIGH_X86_64_LANGUAGE(), result.trace().getBaseLanguage());
} }
} }
protected LaunchConfigurator dbgengFileOnly(String file) {
return new LaunchConfigurator() {
@Override
public Map<String, ?> configureLauncher(TraceRmiLaunchOffer offer,
Map<String, ?> arguments, RelPrompt relPrompt) {
Map<String, Object> args = new HashMap<>(arguments);
args.put("env:OPT_TARGET_IMG", file);
return args;
}
};
}
@Test
public void testLaunchLocalDbgeng() throws Exception {
assumeTrue(OperatingSystem.CURRENT_OPERATING_SYSTEM == OperatingSystem.WINDOWS);
createProgram(getSLEIGH_X86_64_LANGUAGE());
try (Transaction tx = program.openTransaction("Rename")) {
program.setName("notepad.exe");
}
programManager.openProgram(program);
TraceRmiLaunchOffer offer = findByTitle(launcherService.getOffers(program), "dbgeng");
try (LaunchResult result =
offer.launchProgram(new ConsoleTaskMonitor(), dbgengFileOnly("notepad.exe"))) {
if (result.exception() != null) {
throw new AssertionError(result.exception());
}
assertEquals("notepad.exe", result.trace().getName());
assertEquals(getSLEIGH_X86_64_LANGUAGE(), result.trace().getBaseLanguage());
}
}
} }