use std::path::Path;
use jni::{JValue, jni_sig, jni_str, objects::JObject};
use crate::{
AnalysisOptions, AnalysisReport, ProgramLoadInfo, ProgramLoadOptions, ProgramPath, Result,
runtime::GhidraRuntime,
};
use super::support::java_monitor_timeout_seconds;
use super::{
lifecycle::attach_bridge,
support::{JavaHandle, find_bridge_class, java_string, jni_error, path_string},
};
pub(crate) fn open_or_create_project(
runtime: &GhidraRuntime,
project_location: &Path,
project_name: &str,
) -> Result<JavaHandle> {
attach_bridge(runtime)?;
runtime.with_attached(|env| -> Result<JavaHandle> {
let project_location = env.new_string(path_string(project_location))?;
let project_name = env.new_string(project_name)?;
let class = find_bridge_class(env)?;
let value = match env.call_static_method(
class,
jni_str!("openOrCreateProject"),
jni_sig!("(Ljava/lang/String;Ljava/lang/String;)J"),
&[
JValue::Object(&JObject::from(project_location)),
JValue::Object(&JObject::from(project_name)),
],
) {
Ok(value) => value,
Err(error) => {
return Err(jni_error(
env,
"failed to open or create Ghidra project",
error,
));
}
};
Ok(value.j()?)
})
}
pub(crate) struct BridgeLoadedProgram {
pub(crate) handle: JavaHandle,
pub(crate) info: ProgramLoadInfo,
}
#[derive(serde::Deserialize)]
#[serde(deny_unknown_fields)]
struct BridgeLoadedProgramJson {
handle: JavaHandle,
info: ProgramLoadInfo,
}
pub(crate) fn open_program(
runtime: &GhidraRuntime,
project: JavaHandle,
path: &ProgramPath,
) -> Result<JavaHandle> {
attach_bridge(runtime)?;
runtime.with_attached(|env| -> Result<JavaHandle> {
let folder = env.new_string(path.folder())?;
let name = env.new_string(path.name())?;
let class = find_bridge_class(env)?;
let value = match env.call_static_method(
class,
jni_str!("openProgram"),
jni_sig!("(JLjava/lang/String;Ljava/lang/String;)J"),
&[
JValue::Long(project),
JValue::Object(&JObject::from(folder)),
JValue::Object(&JObject::from(name)),
],
) {
Ok(value) => value,
Err(error) => {
return Err(jni_error(env, "failed to open Ghidra program", error));
}
};
Ok(value.j()?)
})
}
pub(crate) fn open_or_import_program(
runtime: &GhidraRuntime,
project: JavaHandle,
options: &ProgramLoadOptions,
) -> Result<BridgeLoadedProgram> {
attach_bridge(runtime)?;
runtime.with_attached(|env| -> Result<BridgeLoadedProgram> {
let binary = JObject::from(env.new_string(path_string(&options.binary))?);
let folder = JObject::from(env.new_string(options.path.folder())?);
let name = JObject::from(env.new_string(options.path.name())?);
let loader = optional_string(env, options.loader.as_deref())?;
let language = optional_string(env, options.language.as_deref())?;
let compiler = optional_string(env, options.compiler.as_deref())?;
let monitor_timeout_seconds = java_monitor_timeout_seconds(&options.monitor)?;
let loader_args = string_array(
env,
options
.loader_args
.iter()
.flat_map(|arg| [arg.name.as_str(), arg.value.as_str()]),
)?;
let loader_args = JObject::from(loader_args);
let class = find_bridge_class(env)?;
let value = match env.call_static_method(
class,
jni_str!("openOrImportProgram"),
jni_sig!(
"(JLjava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;[Ljava/lang/String;I)Ljava/lang/String;"
),
&[
JValue::Long(project),
JValue::Object(&binary),
JValue::Object(&folder),
JValue::Object(&name),
JValue::Object(&loader),
JValue::Object(&language),
JValue::Object(&compiler),
JValue::Object(&loader_args),
JValue::Int(monitor_timeout_seconds),
],
) {
Ok(value) => value,
Err(error) => {
return Err(jni_error(
env,
"failed to open or import Ghidra program",
error,
));
}
};
let json = java_string(env, value.l()?)?;
let loaded = serde_json::from_str::<BridgeLoadedProgramJson>(&json).map_err(|source| {
crate::Error::json("failed to parse Ghidra loaded program metadata", source)
})?;
Ok(BridgeLoadedProgram {
handle: loaded.handle,
info: loaded.info,
})
})
}
pub(crate) fn analyze_program(
runtime: &GhidraRuntime,
project: JavaHandle,
program: JavaHandle,
options: AnalysisOptions,
) -> Result<AnalysisReport> {
attach_bridge(runtime)?;
runtime.with_attached(|env| -> Result<AnalysisReport> {
let mode = env.new_string(options.mode.as_bridge_str())?;
let monitor_timeout_seconds = java_monitor_timeout_seconds(&options.monitor)?;
let class = find_bridge_class(env)?;
let value = match env.call_static_method(
class,
jni_str!("analyzeProgram"),
jni_sig!("(JJLjava/lang/String;I)Ljava/lang/String;"),
&[
JValue::Long(project),
JValue::Long(program),
JValue::Object(&JObject::from(mode)),
JValue::Int(monitor_timeout_seconds),
],
) {
Ok(value) => value,
Err(error) => return Err(jni_error(env, "failed to analyze Ghidra program", error)),
};
let json = java_string(env, value.l()?)?;
serde_json::from_str(&json)
.map_err(|source| crate::Error::json("failed to parse Ghidra analysis report", source))
})
}
pub(crate) fn save_program(
runtime: &GhidraRuntime,
project: JavaHandle,
program: JavaHandle,
) -> Result<()> {
program_operation(
runtime,
project,
program,
jni_str!("saveProgram"),
"failed to save Ghidra program",
)
}
fn optional_string<'local>(
env: &mut jni::Env<'local>,
value: Option<&str>,
) -> Result<JObject<'local>> {
match value {
Some(value) => Ok(JObject::from(env.new_string(value)?)),
None => Ok(JObject::null()),
}
}
fn string_array<'local, I>(
env: &mut jni::Env<'local>,
values: I,
) -> Result<jni::objects::JObjectArray<'local>>
where
I: IntoIterator,
I::Item: AsRef<str>,
{
let values = values.into_iter().collect::<Vec<_>>();
let array = env.new_object_array(
values.len() as i32,
jni_str!("java/lang/String"),
JObject::null(),
)?;
for (index, value) in values.iter().enumerate() {
let value = env.new_string(value.as_ref())?;
array.set_element(env, index, &value)?;
}
Ok(array)
}
pub(crate) fn close_program(
runtime: &GhidraRuntime,
project: JavaHandle,
program: JavaHandle,
) -> Result<()> {
program_operation(
runtime,
project,
program,
jni_str!("closeProgram"),
"failed to close Ghidra program",
)
}
pub(crate) fn close_project(runtime: &GhidraRuntime, project: JavaHandle) -> Result<()> {
attach_bridge(runtime)?;
runtime.with_attached(|env| -> Result<()> {
let class = find_bridge_class(env)?;
match env.call_static_method(
class,
jni_str!("closeProject"),
jni_sig!("(J)V"),
&[JValue::Long(project)],
) {
Ok(_) => Ok(()),
Err(error) => Err(jni_error(env, "failed to close Ghidra project", error)),
}
})
}
pub(crate) fn program_name(runtime: &GhidraRuntime, program: JavaHandle) -> Result<String> {
string_operation(
runtime,
program,
jni_str!("programName"),
"failed to read Ghidra program name",
)
}
fn program_operation(
runtime: &GhidraRuntime,
project: JavaHandle,
program: JavaHandle,
method: &'static jni::strings::JNIStr,
context: &'static str,
) -> Result<()> {
attach_bridge(runtime)?;
runtime.with_attached(|env| -> Result<()> {
let class = find_bridge_class(env)?;
match env.call_static_method(
class,
method,
jni_sig!("(JJ)V"),
&[JValue::Long(project), JValue::Long(program)],
) {
Ok(_) => Ok(()),
Err(error) => Err(jni_error(env, context, error)),
}
})
}
fn string_operation(
runtime: &GhidraRuntime,
program: JavaHandle,
method: &'static jni::strings::JNIStr,
context: &'static str,
) -> Result<String> {
attach_bridge(runtime)?;
runtime.with_attached(|env| -> Result<String> {
let class = find_bridge_class(env)?;
let value = match env.call_static_method(
class,
method,
jni_sig!("(J)Ljava/lang/String;"),
&[JValue::Long(program)],
) {
Ok(value) => value,
Err(error) => return Err(jni_error(env, context, error)),
};
java_string(env, value.l()?)
})
}