Skip to content

Commit

Permalink
Add debuginfod support to dwarf_import
Browse files Browse the repository at this point in the history
  • Loading branch information
negasora committed May 31, 2024
1 parent 725d445 commit 5c7ab96
Show file tree
Hide file tree
Showing 3 changed files with 199 additions and 14 deletions.
132 changes: 131 additions & 1 deletion rust/examples/dwarf/dwarf_import/src/helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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},
Expand Down Expand Up @@ -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());
}
71 changes: 58 additions & 13 deletions rust/examples/dwarf/dwarf_import/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ use binaryninja::{
binaryview::{BinaryView, BinaryViewExt},
debuginfo::{CustomDebugInfoParser, DebugInfo, DebugInfoParser},
logger,
settings::Settings,
templatesimplifier::simplify_str_to_str,
};
use dwarfreader::{
Expand Down Expand Up @@ -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 =
Expand All @@ -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
Expand All @@ -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(
Expand All @@ -278,17 +280,60 @@ 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
}
}
}
}

#[no_mangle]
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
}
10 changes: 10 additions & 0 deletions rust/examples/dwarf/shared/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ use binaryninja::{
binaryview::{BinaryView, BinaryViewBase, BinaryViewExt},
databuffer::DataBuffer,
Endianness,
settings::Settings,
};

use std::{ffi::CString, rc::Rc};
Expand Down Expand Up @@ -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)
Expand Down

0 comments on commit 5c7ab96

Please sign in to comment.