summaryrefslogtreecommitdiff
path: root/rust/examples
diff options
context:
space:
mode:
authorJosh Ferrell <josh@vector35.com>2024-05-31 16:17:01 -0400
committerJosh F <negasora@users.noreply.github.com>2024-05-31 18:31:12 -0400
commit5c7ab961657c6bcfc58c84cf93f850bdb102a425 (patch)
tree4504372bf06fd286fe869f7281d17cfbce5a59fc /rust/examples
parent725d4450e850eb64d4c3fcda7ae567750f4c741f (diff)
Add debuginfod support to dwarf_import
Diffstat (limited to 'rust/examples')
-rw-r--r--rust/examples/dwarf/dwarf_import/src/helpers.rs132
-rw-r--r--rust/examples/dwarf/dwarf_import/src/lib.rs71
-rw-r--r--rust/examples/dwarf/shared/src/lib.rs10
3 files changed, 199 insertions, 14 deletions
diff --git a/rust/examples/dwarf/dwarf_import/src/helpers.rs b/rust/examples/dwarf/dwarf_import/src/helpers.rs
index ed40d446..ae338a4b 100644
--- a/rust/examples/dwarf/dwarf_import/src/helpers.rs
+++ b/rust/examples/dwarf/dwarf_import/src/helpers.rs
@@ -12,8 +12,18 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-use crate::DebugInfoBuilderContext;
+use std::{
+ collections::HashMap,
+ ops::Deref,
+ sync::mpsc,
+ str::FromStr
+};
+use crate::DebugInfoBuilderContext;
+use binaryninja::binaryview::BinaryViewBase;
+use binaryninja::filemetadata::FileMetadata;
+use binaryninja::Endianness;
+use binaryninja::{binaryview::{BinaryView, BinaryViewExt}, downloadprovider::{DownloadInstanceInputOutputCallbacks, DownloadProvider}, rc::Ref, settings::Settings};
use gimli::{
constants, Attribute, AttributeValue,
AttributeValue::{DebugInfoRef, UnitRef},
@@ -291,3 +301,123 @@ pub(crate) fn get_expr_value<R: Reader<Offset = usize>>(
None
}
}
+
+
+pub(crate) fn download_debug_info(view: &BinaryView) -> Result<Ref<BinaryView>, String> {
+ let settings = Settings::new("");
+
+ let mut build_id: Option<String> = None;
+
+ if let Ok(raw_view) = view.raw_view() {
+ if let Ok(build_id_section) = raw_view.section_by_name(".note.gnu.build-id") {
+ // Name size - 4 bytes
+ // Desc size - 4 bytes
+ // Type - 4 bytes
+ // Name - n bytes
+ // Desc - n bytes
+ let build_id_bytes = raw_view.read_vec(build_id_section.start(), build_id_section.len());
+ if build_id_bytes.len() < 12 {
+ return Err("Build id section must be at least 12 bytes".to_string());
+ }
+
+ let name_len: u32;
+ let desc_len: u32;
+ let note_type: u32;
+ match raw_view.default_endianness() {
+ Endianness::LittleEndian => {
+ name_len = u32::from_le_bytes(build_id_bytes[0..4].try_into().unwrap());
+ desc_len = u32::from_le_bytes(build_id_bytes[4..8].try_into().unwrap());
+ note_type = u32::from_le_bytes(build_id_bytes[8..12].try_into().unwrap());
+ },
+ Endianness::BigEndian => {
+ name_len = u32::from_be_bytes(build_id_bytes[0..4].try_into().unwrap());
+ desc_len = u32::from_be_bytes(build_id_bytes[4..8].try_into().unwrap());
+ note_type = u32::from_be_bytes(build_id_bytes[8..12].try_into().unwrap());
+ }
+ };
+
+ if note_type != 3 {
+ return Err(format!("Build id section has wrong type: {}", note_type));
+ }
+
+ let expected_len = (12 + name_len + desc_len) as usize;
+
+ if build_id_bytes.len() < expected_len {
+ return Err(format!("Build id section not expected length: expected {}, got {}", expected_len, build_id_bytes.len()));
+ }
+
+ let desc: &[u8] = &build_id_bytes[(12+name_len as usize)..expected_len];
+ build_id = Some(desc.iter().map(|b| format!("{:02x}", b)).collect());
+ }
+ }
+
+ if build_id.is_none() {
+ return Err("Failed to get build id".to_string());
+ }
+
+ let debug_server_urls = settings.get_string_list("network.debuginfodServers", Some(view), None);
+
+ for debug_server_url in debug_server_urls.iter() {
+ let artifact_url = format!("{}/buildid/{}/debuginfo", debug_server_url, build_id.as_ref().unwrap());
+
+ // Download from remote
+ let (tx, rx) = mpsc::channel();
+ let write = move |data: &[u8]| -> usize {
+ if let Ok(_) = tx.send(Vec::from(data)) {
+ data.len()
+ } else {
+ 0
+ }
+ };
+
+ let dp = DownloadProvider::try_default().map_err(|_| "No default download provider")?;
+ let mut inst = dp
+ .create_instance()
+ .map_err(|_| "Couldn't create download instance")?;
+ let result = inst
+ .perform_custom_request(
+ "GET",
+ artifact_url,
+ HashMap::<String, String>::new(),
+ DownloadInstanceInputOutputCallbacks {
+ read: None,
+ write: Some(Box::new(write)),
+ progress: None,
+ },
+ )
+ .map_err(|e| e.to_string())?;
+ if result.status_code != 200 {
+ continue;
+ }
+
+ let mut expected_length = None;
+ for (k, v) in result.headers.iter() {
+ if k.to_lowercase() == "content-length" {
+ expected_length = Some(usize::from_str(v).map_err(|e| e.to_string())?);
+ }
+ }
+
+ let mut data = vec![];
+ while let Ok(packet) = rx.try_recv() {
+ data.extend(packet.into_iter());
+ }
+
+ if let Some(length) = expected_length {
+ if data.len() != length {
+ return Err(format!(
+ "Bad length: expected {} got {}",
+ length,
+ data.len()
+ ));
+ }
+ }
+
+ let options = "{\"analysis.debugInfo.internal\": false}";
+ let bv = BinaryView::from_data(FileMetadata::new().deref(), &data)
+ .map_err(|_| "Unable to create binary view from downloaded data".to_string())?;
+
+ return binaryninja::load_view(bv.deref(), false, Some(options))
+ .ok_or("Unable to load binary view from downloaded data".to_string());
+ }
+ return Err("Could not find a server with debug info for this file".to_string());
+}
diff --git a/rust/examples/dwarf/dwarf_import/src/lib.rs b/rust/examples/dwarf/dwarf_import/src/lib.rs
index 8aeac658..a752d351 100644
--- a/rust/examples/dwarf/dwarf_import/src/lib.rs
+++ b/rust/examples/dwarf/dwarf_import/src/lib.rs
@@ -27,6 +27,7 @@ use binaryninja::{
binaryview::{BinaryView, BinaryViewExt},
debuginfo::{CustomDebugInfoParser, DebugInfo, DebugInfoParser},
logger,
+ settings::Settings,
templatesimplifier::simplify_str_to_str,
};
use dwarfreader::{
@@ -213,21 +214,22 @@ fn parse_unit<R: Reader<Offset = usize>>(
}
fn parse_dwarf(
- view: &BinaryView,
+ bv: &BinaryView,
progress: Box<dyn Fn(usize, usize) -> Result<(), ()>>,
-) -> DebugInfoBuilder {
+) -> Result<DebugInfoBuilder, ()> {
// Determine if this is a DWO
// TODO : Make this more robust...some DWOs follow non-DWO conventions
- let dwo_file = is_dwo_dwarf(view) || is_raw_dwo_dwarf(view);
// Figure out if it's the given view or the raw view that has the dwarf info in it
- let raw_view = &view.raw_view().unwrap();
- let view = if is_dwo_dwarf(view) || is_non_dwo_dwarf(view) {
- view
+ let raw_view = &bv.raw_view().unwrap();
+ let view = if is_dwo_dwarf(bv) || is_non_dwo_dwarf(bv) {
+ bv
} else {
raw_view
};
+ let dwo_file = is_dwo_dwarf(view) || is_raw_dwo_dwarf(view);
+
// gimli setup
let endian = get_endian(view);
let mut section_reader =
@@ -246,7 +248,7 @@ fn parse_dwarf(
if !recover_names(&mut debug_info_builder_context, &progress)
|| debug_info_builder_context.total_die_count == 0
{
- return debug_info_builder;
+ return Ok(debug_info_builder);
}
// Parse all the compilation units
@@ -261,14 +263,14 @@ fn parse_dwarf(
);
}
}
- debug_info_builder
+ Ok(debug_info_builder)
}
struct DWARFParser;
impl CustomDebugInfoParser for DWARFParser {
fn is_valid(&self, view: &BinaryView) -> bool {
- dwarfreader::is_valid(view)
+ dwarfreader::is_valid(view) || dwarfreader::can_use_debuginfod(view)
}
fn parse_info(
@@ -278,10 +280,28 @@ impl CustomDebugInfoParser for DWARFParser {
debug_file: &BinaryView,
progress: Box<dyn Fn(usize, usize) -> Result<(), ()>>,
) -> bool {
- parse_dwarf(debug_file, progress)
- .post_process(bv, debug_info)
- .commit_info(debug_info);
- true
+ let external_file = if !dwarfreader::is_valid(bv) && dwarfreader::can_use_debuginfod(bv) {
+ //TODO: try to download from debuginfod if there is no debug info in the binary
+ if let Ok(debug_view) = helpers::download_debug_info(bv) {
+ Some(debug_view)
+ } else {
+ None
+ }
+ } else {
+ None
+ };
+
+ match parse_dwarf(external_file.as_deref().unwrap_or(debug_file), progress) {
+ Ok(mut builder) => {
+ builder
+ .post_process(bv, debug_info)
+ .commit_info(debug_info);
+ true
+ },
+ Err(_) => {
+ false
+ }
+ }
}
}
@@ -289,6 +309,31 @@ impl CustomDebugInfoParser for DWARFParser {
pub extern "C" fn CorePluginInit() -> bool {
logger::init(LevelFilter::Debug).unwrap();
+ let settings = Settings::new("");
+
+ settings.register_setting_json(
+ "network.enableDebuginfod",
+ r#"{
+ "title" : "Enable Debuginfod Support",
+ "type" : "boolean",
+ "default" : false,
+ "description" : "Enable using debuginfod servers to fetch debug info for files with a .note.gnu.build-id section.",
+ "ignore" : []
+ }"#,
+ );
+
+ settings.register_setting_json(
+ "network.debuginfodServers",
+ r#"{
+ "title" : "Debuginfod Server URLs",
+ "type" : "array",
+ "elementType" : "string",
+ "default" : [],
+ "description" : "Servers to use for fetching debug info for files with a .note.gnu.build-id section.",
+ "ignore" : []
+ }"#,
+ );
+
DebugInfoParser::register("DWARF", DWARFParser {});
true
}
diff --git a/rust/examples/dwarf/shared/src/lib.rs b/rust/examples/dwarf/shared/src/lib.rs
index 7712ff3b..76c903e4 100644
--- a/rust/examples/dwarf/shared/src/lib.rs
+++ b/rust/examples/dwarf/shared/src/lib.rs
@@ -20,6 +20,7 @@ use binaryninja::{
binaryview::{BinaryView, BinaryViewBase, BinaryViewExt},
databuffer::DataBuffer,
Endianness,
+ settings::Settings,
};
use std::{ffi::CString, rc::Rc};
@@ -52,6 +53,15 @@ pub fn is_raw_dwo_dwarf(view: &BinaryView) -> bool {
}
}
+pub fn can_use_debuginfod(view: &BinaryView) -> bool {
+ if let Ok(raw_view) = view.raw_view() {
+ if raw_view.section_by_name(".note.gnu.build-id").is_ok() {
+ return Settings::new("").get_bool("network.enableDebuginfod", Some(view), None);
+ }
+ }
+ false
+}
+
pub fn is_valid(view: &BinaryView) -> bool {
is_non_dwo_dwarf(view)
|| is_raw_non_dwo_dwarf(view)