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
##MODULE IP: Apache License 2.0
Module.manifest||GHIDRA||||END|
src/javaprovider/def/javaprovider.def||GHIDRA||||END|
src/javaprovider/rc/javaprovider.rc||GHIDRA||||END|
data/debugger-launchers/local-dbgeng.bat||GHIDRA||||END|
src/main/py/LICENSE||GHIDRA||||END|
src/main/py/README.md||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 time
import sys
import re
from ghidratrace import sch
from ghidratrace.client import Client, Address, AddressRange, TraceObject
@@ -185,7 +186,7 @@ def compute_name(progname=None):
progname = buffer.decode('utf-8')
except Exception:
return 'pydbg/noname'
return 'pydbg/' + progname.split('/')[-1]
return 'pydbg/' + re.split(r'/|\\', progname)[-1]
def start_trace(name):
@@ -1301,7 +1302,36 @@ def ghidra_util_wait_stopped(timeout=1):
time.sleep(0.1)
if time.time() - start > timeout:
raise RuntimeError('Timed out waiting for thread to stop')
def dbg():
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')
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)
dbg().stepi(n)
@@ -511,7 +511,7 @@ def write_mem(process: sch.Schema('Process'), address: Address, data: bytes):
@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."""
util.select_frame()
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,
Collection<TerminalSession> subordinates)
throws IOException {
File workingDirectory, Collection<TerminalSession> subordinates) throws IOException {
PtyFactory factory = getPtyFactory();
Pty pty = factory.openpty();
@@ -432,13 +431,19 @@ public abstract class AbstractTraceRmiLaunchOffer implements TraceRmiLaunchOffer
TerminalListener resizeListener = new TerminalListener() {
@Override
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);
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(() -> {
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() {
Program program = plugin.currentProgram;
if (program == null) {
return null;
}
ConfigLast best = null;
ProgramUserData userData = program.getProgramUserData();
@@ -113,14 +116,16 @@ public class LaunchAction extends MultiActionDockingAction {
List<DockingActionIf> actions = new ArrayList<>();
ProgramUserData userData = program.getProgramUserData();
Map<String, Long> saved = new HashMap<>();
for (String propName : userData.getStringPropertyNames()) {
ConfigLast check = checkSavedConfig(userData, propName);
if (check == null) {
continue;
if (program != null) {
ProgramUserData userData = program.getProgramUserData();
for (String propName : userData.getStringPropertyNames()) {
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) {
@@ -134,6 +139,8 @@ public class LaunchAction extends MultiActionDockingAction {
.build());
Long last = saved.get(offer.getConfigName());
if (last == null) {
// NB. If program == null, this will always happen.
// Thus, no worries about program.getName() below.
continue;
}
actions.add(new ActionBuilder(offer.getConfigName(), plugin.getName())
@@ -172,6 +179,11 @@ public class LaunchAction extends MultiActionDockingAction {
// Make accessible to this file
return super.showPopup();
}
@Override
public String getToolTipText() {
return getDescription();
}
}
@Override
@@ -180,19 +192,45 @@ public class LaunchAction extends MultiActionDockingAction {
}
@Override
public void actionPerformed(ActionContext context) {
// See comment on super method about use of runLater
ConfigLast last = findMostRecentConfig();
public boolean isEnabledForContext(ActionContext context) {
return plugin.currentProgram != null;
}
protected TraceRmiLaunchOffer findOffer(ConfigLast last) {
if (last == null) {
Swing.runLater(() -> button.showPopup());
return;
return null;
}
for (TraceRmiLaunchOffer offer : plugin.getOffers(plugin.currentProgram)) {
if (offer.getConfigName().equals(last.configName)) {
plugin.relaunch(offer);
return;
return offer;
}
}
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 generic.jar.ResourceFile;
import ghidra.app.plugin.core.debug.DebuggerPluginPackage;
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.util.HelpLocation;
import ghidra.util.Msg;
public class UnixShellScriptTraceRmiLaunchOpinion 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));
}
public class UnixShellScriptTraceRmiLaunchOpinion 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(".sh"))))
.flatMap(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();
}
})
.flatMap(sf -> createOffer(plugin, program, sf))
.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.SocketAddress;
import java.nio.ByteBuffer;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.Instant;
import java.time.format.DateTimeFormatter;
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 {
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 {
if (path == null && index == 0 || index == path.getNameCount()) {
if (path == null && index == 0 || index == path.size()) {
return parent;
}
String name = path.getName(index).toString();
String name = path.get(index);
return createFolders(getOrCreateFolder(parent, name), path, index + 1);
}
@@ -859,10 +857,10 @@ public class TraceRmiHandler implements TraceRmiConnection {
protected ReplyCreateTrace handleCreateTrace(RequestCreateTrace req)
throws InvalidNameException, IOException, CancelledException {
DomainFolder traces = getOrCreateNewTracesFolder();
Path path = Paths.get(req.getPath().getPath());
DomainFolder folder = createFolders(traces, path.getParent());
List<String> path = sanitizePath(req.getPath().getPath());
DomainFolder folder = createFolders(traces, path.subList(0, path.size() - 1));
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);
DoId doId = requireAvailableDoId(req.getOid());
openTraces.put(new OpenTrace(doId, trace, target));
@@ -870,6 +868,10 @@ public class TraceRmiHandler implements TraceRmiConnection {
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)
throws AddressOverflowException {
OpenTrace open = requireOpenTrace(req.getOid());
@@ -251,7 +251,7 @@ public class DebuggerModulesProvider extends ComponentProviderAdapter {
if (context instanceof DebuggerObjectActionContext ctx) {
return DebuggerModulesPanel.getSelectedModulesFromContext(ctx);
}
return null;
return Set.of();
}
protected static Set<TraceSection> getSelectedSections(ActionContext context) {
@@ -264,7 +264,7 @@ public class DebuggerModulesProvider extends ComponentProviderAdapter {
if (context instanceof DebuggerObjectActionContext ctx) {
return DebuggerModulesPanel.getSelectedSectionsFromContext(ctx);
}
return null;
return Set.of();
}
protected static AddressSetView getSelectedAddresses(ActionContext context) {
@@ -299,7 +299,7 @@ public class DebuggerModulesProvider extends ComponentProviderAdapter {
}
AddressSetView sel = getSelectedAddresses(context);
if (sel == null) {
if (sel == null || sel.isEmpty()) {
return;
}
@@ -540,9 +540,8 @@ public class DebuggerModulesProvider extends ComponentProviderAdapter {
.onAction(this::activatedMapModules)
.buildAndInstallLocal(this);
actionMapModuleTo = MapModuleToAction.builder(plugin)
.withContext(DebuggerModuleActionContext.class)
.enabledWhen(ctx -> currentProgram != null && ctx.getSelectedModules().size() == 1)
.popupWhen(ctx -> currentProgram != null && ctx.getSelectedModules().size() == 1)
.enabledWhen(ctx -> currentProgram != null && getSelectedModules(ctx).size() == 1)
.popupWhen(ctx -> currentProgram != null && getSelectedModules(ctx).size() == 1)
.onAction(this::activatedMapModuleTo)
.buildAndInstallLocal(this);
actionMapSections = MapSectionsAction.builder(plugin)
@@ -551,9 +550,8 @@ public class DebuggerModulesProvider extends ComponentProviderAdapter {
.onAction(this::activatedMapSections)
.buildAndInstallLocal(this);
actionMapSectionTo = MapSectionToAction.builder(plugin)
.withContext(DebuggerSectionActionContext.class)
.enabledWhen(ctx -> currentProgram != null && ctx.getSelectedSections().size() == 1)
.popupWhen(ctx -> currentProgram != null && ctx.getSelectedSections().size() == 1)
.enabledWhen(ctx -> currentProgram != null && getSelectedSections(ctx).size() == 1)
.popupWhen(ctx -> currentProgram != null && getSelectedSections(ctx).size() == 1)
.onAction(this::activatedMapSectionTo)
.buildAndInstallLocal(this);
actionMapSectionsTo = MapSectionsToAction.builder(plugin)
@@ -25,6 +25,7 @@ import ghidra.program.model.listing.Program;
import ghidra.program.model.mem.MemoryBlock;
import ghidra.trace.model.Lifespan;
import ghidra.trace.model.memory.TraceMemoryRegion;
import ghidra.trace.model.memory.TraceObjectMemoryRegion;
import ghidra.trace.model.modules.TraceModule;
public class DefaultModuleMapProposal
@@ -207,10 +208,14 @@ public class DefaultModuleMapProposal
catch (AddressOverflowException e) {
return; // Just score it as having no matches?
}
Lifespan lifespan = module.getLifespan();
for (TraceMemoryRegion region : module.getTrace()
.getMemoryManager()
.getRegionsIntersecting(module.getLifespan(), moduleRange)) {
getMatcher(region.getMinAddress().subtract(moduleBase)).region = region;
.getRegionsIntersecting(lifespan, moduleRange)) {
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;
import java.util.ArrayList;
import java.util.List;
import java.util.*;
import java.util.stream.Collectors;
public class ShellUtils {
enum State {
@@ -139,4 +139,11 @@ public class ShellUtils {
}
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
public AddressRange getRange() {
try (LockHold hold = object.getTrace().lockRead()) {
if (object.getLife().isEmpty()) {
return range;
}
return range = TraceObjectInterfaceUtils.getValue(object, getCreationSnap(),
TargetMemoryRegion.RANGE_ATTRIBUTE_NAME, AddressRange.class, range);
return getRange(getCreationSnap());
}
}
@@ -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
public Address getMinAddress() {
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
public Address getMaxAddress() {
AddressRange range = getRange();
@@ -20,6 +20,7 @@ import java.util.Set;
import ghidra.dbg.target.TargetMemoryRegion;
import ghidra.dbg.target.TargetObject;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressRange;
import ghidra.trace.model.Lifespan;
import ghidra.trace.model.target.TraceObjectInterface;
@@ -39,6 +40,12 @@ public interface TraceObjectMemoryRegion extends TraceMemoryRegion, TraceObjectI
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 addFlags(Lifespan lifespan, Collection<TraceMemoryFlag> flags);
@@ -15,6 +15,7 @@
*/
package ghidra.pty;
import java.io.File;
import java.io.IOException;
import java.util.*;
@@ -51,19 +52,28 @@ public interface PtyChild extends PtyEndpoint {
*
* @param args the image path and arguments
* @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.
* @return a handle to the subprocess
* @throws IOException if the session could not be started
*/
PtySession session(String[] args, Map<String, String> env, Collection<TermMode> mode)
throws IOException;
PtySession session(String[] args, Map<String, String> env, File workingDirectory,
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)
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.
*/
@Override
public PtySession session(String[] args, Map<String, String> env, Collection<TermMode> mode)
throws IOException {
return sessionUsingJavaLeader(args, env, mode);
public PtySession session(String[] args, Map<String, String> env, File workingDirectory,
Collection<TermMode> mode) throws IOException {
return sessionUsingJavaLeader(args, env, workingDirectory, mode);
}
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<>();
String javaCommand =
System.getProperty("java.home") + File.separator + "bin" + File.separator + "java";
@@ -78,6 +78,9 @@ public class LinuxPtyChild extends LinuxPtyEndpoint implements PtyChild {
if (env != null) {
builder.environment().putAll(env);
}
if (workingDirectory != null) {
builder.directory(workingDirectory);
}
builder.inheritIO();
applyMode(mode);
@@ -48,8 +48,11 @@ public class SshPtyChild extends SshPtyEndpoint implements PtyChild {
}
@Override
public SshPtySession session(String[] args, Map<String, String> env, Collection<TermMode> mode)
throws IOException {
public SshPtySession session(String[] args, Map<String, String> env, File workingDirectory,
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
* universal. This certainly works for my version of bash :)
@@ -15,6 +15,7 @@
*/
package ghidra.pty.windows;
import java.io.File;
import java.io.IOException;
import java.util.*;
@@ -75,7 +76,7 @@ public class ConPtyChild extends ConPtyEndpoint implements PtyChild {
@Override
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?
*
@@ -91,9 +92,11 @@ public class ConPtyChild extends ConPtyEndpoint implements PtyChild {
null /*lpProcessAttributes*/,
null /*lpThreadAttributes*/,
false /*bInheritHandles*/,
ConPty.EXTENDED_STARTUPINFO_PRESENT /*dwCreationFlags*/,
null /*lpEnvironment*/,
null /*lpCurrentDirectory*/,
new DWORD(Kernel32.EXTENDED_STARTUPINFO_PRESENT |
Kernel32.CREATE_UNICODE_ENVIRONMENT) /*dwCreationFlags*/,
env == null ? null : new WString(ShellUtils.generateEnvBlock(env)),
workingDirectory == null ? null
: new WString(workingDirectory.getAbsolutePath()) /*lpCurrentDirectory*/,
si /*lpStartupInfo*/,
pi /*lpProcessInformation*/).booleanValue()) {
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 {
byte[] temp = new byte[len];
int read = read(temp);
System.arraycopy(temp, 0, b, off, read);
if (read > 0) {
System.arraycopy(temp, 0, b, off, read);
}
return read;
}
@@ -57,7 +57,7 @@ public interface ConsoleApiNative extends StdCallLibrary {
WinBase.SECURITY_ATTRIBUTES lpThreadAttributes,
boolean bInheritHandles,
DWORD dwCreationFlags,
Pointer lpEnvironment,
WString lpEnvironment,
WString lpCurrentDirectory,
STARTUPINFOEX lpStartupInfo,
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.assertFalse;
import static org.junit.Assume.assumeTrue;
import java.util.*;
@@ -28,6 +29,7 @@ import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerTest;
import ghidra.app.services.TraceRmiLauncherService;
import ghidra.debug.api.tracermi.TraceRmiLaunchOffer;
import ghidra.debug.api.tracermi.TraceRmiLaunchOffer.*;
import ghidra.framework.OperatingSystem;
import ghidra.util.task.ConsoleTaskMonitor;
public class TraceRmiLauncherServicePluginTest extends AbstractGhidraHeadedDebuggerTest {
@@ -50,7 +52,7 @@ public class TraceRmiLauncherServicePluginTest extends AbstractGhidraHeadedDebug
assertFalse(launcherService.getOffers(program).isEmpty());
}
protected LaunchConfigurator fileOnly(String file) {
protected LaunchConfigurator gdbFileOnly(String file) {
return new LaunchConfigurator() {
@Override
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
public void testLaunchLocalGdb() throws Exception {
assumeTrue(OperatingSystem.CURRENT_OPERATING_SYSTEM == OperatingSystem.LINUX);
createProgram(getSLEIGH_X86_64_LANGUAGE());
try (Transaction tx = program.openTransaction("Rename")) {
program.setName("bash");
}
programManager.openProgram(program);
TraceRmiLaunchOffer gdbOffer = findByTitle(launcherService.getOffers(program), "gdb");
TraceRmiLaunchOffer offer = findByTitle(launcherService.getOffers(program), "gdb");
try (LaunchResult result =
gdbOffer.launchProgram(new ConsoleTaskMonitor(), fileOnly("/usr/bin/bash"))) {
offer.launchProgram(new ConsoleTaskMonitor(), gdbFileOnly("/usr/bin/bash"))) {
if (result.exception() != null) {
throw new AssertionError(result.exception());
}
@@ -83,4 +87,39 @@ public class TraceRmiLauncherServicePluginTest extends AbstractGhidraHeadedDebug
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());
}
}
}