Skip to content

Commit b2b4dc4

Browse files
committed
test
1 parent 759f0eb commit b2b4dc4

6 files changed

Lines changed: 304 additions & 6 deletions

File tree

Cargo.lock

Lines changed: 34 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ members = [
99
"arch/riscv",
1010
"arch/msp430",
1111
"rust/plugin_examples/data_renderer",
12+
"rust/plugin_examples/source_line_renderer",
1213
"view/minidump",
1314
"plugins/dwarf/dwarf_import",
1415
"plugins/dwarf/dwarf_import/demo",
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
[package]
2+
name = "source_line_renderer"
3+
version = "0.1.0"
4+
edition = "2021"
5+
publish = false
6+
7+
[lib]
8+
crate-type = ["cdylib"]
9+
10+
[dependencies]
11+
binaryninjacore-sys = { path = "../../binaryninjacore-sys" }
12+
binaryninja = { path = "../.." }
13+
gimli = "0.33.0"
14+
tracing = "0.1"
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
fn main() {
2+
let link_path = std::env::var_os("DEP_BINARYNINJACORE_PATH")
3+
.expect("DEP_BINARYNINJACORE_PATH not specified");
4+
5+
println!("cargo::rustc-link-lib=dylib=binaryninjacore");
6+
println!("cargo::rustc-link-search={}", link_path.to_str().unwrap());
7+
8+
#[cfg(target_os = "linux")]
9+
{
10+
println!(
11+
"cargo::rustc-link-arg=-Wl,-rpath,{0},-L{0}",
12+
link_path.to_string_lossy()
13+
);
14+
}
15+
16+
#[cfg(target_os = "macos")]
17+
{
18+
let crate_name = std::env::var("CARGO_PKG_NAME").expect("CARGO_PKG_NAME not set");
19+
let lib_name = crate_name.replace('-', "_");
20+
println!(
21+
"cargo::rustc-link-arg=-Wl,-install_name,@rpath/lib{}.dylib",
22+
lib_name
23+
);
24+
}
25+
}
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
use binaryninja::binary_view::BinaryView;
2+
use binaryninja::disassembly::{InstructionTextToken, InstructionTextTokenKind};
3+
use binaryninja::file_metadata::SessionId;
4+
use binaryninja::linear_view::{LinearDisassemblyLine, LinearViewObject};
5+
use binaryninja::object_destructor::ObjectDestructor;
6+
use binaryninja::render_layer::{register_render_layer, RenderLayer, RenderLayerDefaultState};
7+
use std::collections::HashMap;
8+
use std::sync::RwLock;
9+
10+
pub mod state;
11+
12+
#[derive(Default)]
13+
struct DebugLineRenderLayer {
14+
data: RwLock<HashMap<SessionId, state::DebugLineState>>,
15+
}
16+
17+
impl DebugLineRenderLayer {
18+
pub fn map_line_info(
19+
&self,
20+
view: &BinaryView,
21+
mut line: LinearDisassemblyLine,
22+
) -> LinearDisassemblyLine {
23+
let addr = line.contents.address;
24+
if let Some(info) = self.line_info(view, addr) {
25+
// We only want to add the line number for lines that display an address.
26+
if line.contents.tokens.first().is_some_and(|t| {
27+
t.kind == InstructionTextTokenKind::AddressDisplay { address: addr }
28+
}) {
29+
line.contents.tokens.insert(
30+
0,
31+
InstructionTextToken::new(" ", InstructionTextTokenKind::AddressSeparator),
32+
);
33+
line.contents.tokens.insert(
34+
0,
35+
InstructionTextToken::new(
36+
info.to_string(),
37+
InstructionTextTokenKind::AddressDisplay { address: addr },
38+
),
39+
);
40+
}
41+
}
42+
line
43+
}
44+
45+
pub fn line_info(&self, view: &BinaryView, address: u64) -> Option<state::DebugLineInfo> {
46+
let lock = self.data.read().ok()?;
47+
let state = lock.get(&view.file().session_id())?;
48+
state.line_info(address)
49+
}
50+
51+
// TODO: This is manual probing rn, would prefer to not do like this.
52+
fn probe_state(&self, view: &BinaryView) {
53+
let session_id = view.file().session_id();
54+
let mut lock = self.data.write().unwrap();
55+
if !lock.contains_key(&session_id) {
56+
if let Ok(state) = state::DebugLineState::new(view) {
57+
lock.insert(session_id, state);
58+
}
59+
}
60+
}
61+
}
62+
63+
impl RenderLayer for DebugLineRenderLayer {
64+
fn apply_to_linear_object(
65+
&self,
66+
_object: &mut LinearViewObject,
67+
_prev_object: Option<&mut LinearViewObject>,
68+
_next_object: Option<&mut LinearViewObject>,
69+
lines: Vec<LinearDisassemblyLine>,
70+
) -> Vec<LinearDisassemblyLine> {
71+
let view = match lines.first() {
72+
Some(first_line) => first_line.function.as_ref().map(|f| f.view()),
73+
None => return lines,
74+
};
75+
76+
if let Some(view) = view {
77+
self.probe_state(&view);
78+
let lines = lines
79+
.into_iter()
80+
.map(|line| self.map_line_info(&view, line))
81+
.collect();
82+
lines
83+
} else {
84+
lines
85+
}
86+
}
87+
}
88+
89+
impl ObjectDestructor for DebugLineRenderLayer {
90+
fn destruct_view(&self, view: &BinaryView) {
91+
let mut lock = self.data.write().unwrap();
92+
lock.remove(&view.file().session_id());
93+
}
94+
}
95+
96+
#[allow(non_snake_case)]
97+
#[unsafe(no_mangle)]
98+
pub unsafe extern "C" fn CorePluginInit() -> bool {
99+
binaryninja::tracing_init!();
100+
101+
register_render_layer(
102+
"Debug Lines",
103+
DebugLineRenderLayer::default(),
104+
RenderLayerDefaultState::Disabled,
105+
);
106+
107+
true
108+
}
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
use binaryninja::binary_view::{BinaryView, BinaryViewBase};
2+
use binaryninja::Endianness;
3+
use gimli::{DebugInfoOffset, RunTimeEndian, UnitSectionOffset};
4+
use std::borrow::Cow;
5+
use std::collections::BTreeMap;
6+
7+
pub struct DebugLineInfo {
8+
pub file: String,
9+
pub line_number: u64,
10+
}
11+
12+
impl std::fmt::Display for DebugLineInfo {
13+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
14+
write!(f, "{}:{}", self.file, self.line_number)
15+
}
16+
}
17+
18+
pub struct DebugLineState {
19+
sections: gimli::DwarfSections<Cow<'static, [u8]>>,
20+
endian: RunTimeEndian,
21+
address_mapping: BTreeMap<u64, UnitSectionOffset>,
22+
}
23+
24+
impl DebugLineState {
25+
// TODO: Support supplementary files and networked files.
26+
pub fn new(view: &BinaryView) -> Result<Self, Box<dyn std::error::Error>> {
27+
let section_reader =
28+
|name: &str| -> Result<Cow<'static, [u8]>, Box<dyn std::error::Error>> {
29+
let mut section = view.section_by_name(name);
30+
if section.is_none() {
31+
// Looks like macho uses underscores instead of dots for section names.
32+
let alt_name = name.replace(".", "__");
33+
section = view.section_by_name(alt_name);
34+
}
35+
36+
match section {
37+
Some(section) => Ok(Cow::Owned(view.read_vec(section.start(), section.len()))),
38+
// TODO: Instead of failing, we return empty section so we can continue.
39+
None => Ok(Cow::Owned(Vec::new())),
40+
}
41+
};
42+
43+
let loader = |section: gimli::SectionId| section_reader(section.name());
44+
let dwarf_sections = gimli::DwarfSections::load(loader)?;
45+
46+
let endian = match view.default_endianness() {
47+
Endianness::LittleEndian => RunTimeEndian::Little,
48+
Endianness::BigEndian => RunTimeEndian::Big,
49+
};
50+
51+
let borrow_section = |section| gimli::EndianSlice::new(Cow::as_ref(section), endian);
52+
let dwarf = dwarf_sections.borrow(borrow_section);
53+
54+
// TODO: The row.address() will not be adjusted for the view image base.
55+
// let base_address = view.image_base();
56+
let mut address_mapping = BTreeMap::new();
57+
let mut units = dwarf.units();
58+
while let Ok(Some(unit_header)) = units.next() {
59+
let Ok(unit) = dwarf.unit(unit_header) else {
60+
continue;
61+
};
62+
let Some(line_program) = unit.line_program.as_ref() else {
63+
continue;
64+
};
65+
66+
let mut program_rows = line_program.clone().rows();
67+
while let Ok(Some((_, row))) = program_rows.next_row() {
68+
if row.end_sequence() {
69+
continue;
70+
}
71+
address_mapping.insert(row.address(), unit_header.offset());
72+
}
73+
}
74+
75+
Ok(Self {
76+
sections: dwarf_sections,
77+
endian,
78+
address_mapping,
79+
})
80+
}
81+
82+
pub fn line_info(&self, address: u64) -> Option<DebugLineInfo> {
83+
// TODO: This can approximte the lie number, and should be more refined before shipping, most of
84+
// TODO: it to do with the address mapping being a tree and we selecting for the closest.
85+
let (_, unit_offset) = self.address_mapping.range(..=address).next_back()?;
86+
let borrow_section = |section| gimli::EndianSlice::new(Cow::as_ref(section), self.endian);
87+
let dwarf = self.sections.borrow(borrow_section);
88+
let unit_offset = DebugInfoOffset(unit_offset.0);
89+
let unit_header = dwarf.debug_info.header_from_offset(unit_offset).ok()?;
90+
let unit = dwarf.unit(unit_header).ok()?;
91+
92+
let line_program = unit.line_program.as_ref()?;
93+
let mut rows = line_program.clone().rows();
94+
95+
let mut current_file_index = 0;
96+
let mut current_line = None;
97+
while let Ok(Some((_, row))) = rows.next_row() {
98+
if row.address() > address {
99+
break;
100+
}
101+
if row.end_sequence() {
102+
continue;
103+
}
104+
current_file_index = row.file_index();
105+
if let Some(line) = row.line() {
106+
current_line = Some(line.get());
107+
}
108+
}
109+
110+
let header = line_program.header();
111+
let file = header.file(current_file_index)?;
112+
let mut file_name = "<unknown file>".to_string();
113+
if let Ok(attr_str) = dwarf.attr_string(&unit, file.path_name()) {
114+
file_name = attr_str.to_string_lossy().into_owned();
115+
}
116+
117+
Some(DebugLineInfo {
118+
file: file_name,
119+
line_number: current_line?,
120+
})
121+
}
122+
}

0 commit comments

Comments
 (0)