Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 12 additions & 12 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ members = [
]

[workspace.package]
version = "2.1.0"
version = "2.1.1"
edition = "2021"
rust-version = "1.80"
license = "MIT"
Expand Down
97 changes: 51 additions & 46 deletions crates/codegraph-binary/src/extract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ fn do_extract(
let mut next_id = SYMBOL_BASE + 1;

for entry in &functions {
let addr = entry.offset.unwrap_or(0);
let addr = entry.addr.unwrap_or(0);
let raw_name = entry
.name
.clone()
Expand Down Expand Up @@ -188,7 +188,7 @@ fn do_extract(
if cfg_markers {
build_chains_with_cfg(session, &functions, &maps, &mut chains, &mut calls)?;
} else {
build_chains_from_graph(session, &functions, &maps, &mut chains, &mut calls)?;
build_chains_from_graph(session, &maps, &mut chains, &mut calls)?;
}

// Chain cho symbol không có call (import/string)
Expand Down Expand Up @@ -267,12 +267,25 @@ fn build_chains_with_cfg(
calls: &mut Vec<CallRecord>,
) -> Result<(), Error> {
for entry in functions {
let addr = entry.offset.unwrap_or(0);
let addr = entry.addr.unwrap_or(0);
let Some(&func_id) = maps.fn_by_addr.get(&addr) else {
continue;
};
let ops: Vec<DisasmOp> = session
.cmdj(&format!("pdfj @ {addr}"))?
// addr 0 = entry rác (import/reloc chưa resolve) — pdfj không bao giờ
// trả ops cho địa chỉ này, bỏ qua sớm thay để r2 bắn ERROR ra stderr.
if addr == 0 {
continue;
}
// Một function r2 không disasm được (addr 0, corrupt, stripped…) không
// được làm fail cả binary — bỏ qua nó và chạy tiếp các function còn lại.
let ops_json = match session.cmdj(&format!("pdfj @ {addr}")) {
Ok(v) => v,
Err(e) => {
tracing::warn!("r2 pdfj @ {addr:#x} failed: {e}; bỏ qua function này");
continue;
}
};
let ops: Vec<DisasmOp> = ops_json
.get("ops")
.and_then(|o| o.as_array())
.cloned()
Expand All @@ -285,7 +298,7 @@ fn build_chains_with_cfg(
let mut seen = HashSet::new();

for op in &ops {
let off = op.offset.unwrap_or(0);
let off = op.addr.unwrap_or(0);
seen.insert(off);
if let Some(t) = &op.type_ {
match t.as_str() {
Expand Down Expand Up @@ -334,47 +347,53 @@ fn build_chains_with_cfg(
Ok(())
}

/// Xây chain nhẹ từ `agCj` (call graph edges) — không có marker CFG.
/// Xây chain nhẹ từ `agCj` (call graph) — không có marker CFG.
/// r2 6.x trả danh sách `{name, imports: [callee names]}` thay vì edges có địa chỉ.
fn build_chains_from_graph(
session: &mut dyn R2Client,
functions: &[FnEntry],
maps: &FnMaps,
chains: &mut HashMap<u64, Vec<u64>>,
calls: &mut Vec<CallRecord>,
) -> Result<(), Error> {
let edges: Vec<CallGraphEdge> = session
.cmdj("agCj")?
.get("edges")
.and_then(|e| e.as_array())
.cloned()
.unwrap_or_default()
.into_iter()
.filter_map(|v| serde_json::from_value::<CallGraphEdge>(v).ok())
.collect();
let nodes: Vec<CallGraphNode> = parse_array(session.cmdj("agCj")?)?;

let mut by_caller: HashMap<u64, Vec<u64>> = HashMap::new();
for edge in &edges {
let from = edge.from.unwrap_or(0);
let to = edge.to.unwrap_or(0);
by_caller.entry(from).or_default().push(to);
// Map tên symbol (đã strip prefix "sym.") → id, cho cả function lẫn import.
let mut name_to_id: HashMap<String, u64> = HashMap::new();
for (&id, name) in maps.fn_id_to_name.iter() {
name_to_id.entry(name.clone()).or_insert(id);
}
for (clean, &id) in maps.import_name_to_id.iter() {
name_to_id.entry(clean.clone()).or_insert(id);
}

for entry in functions {
let addr = entry.offset.unwrap_or(0);
let Some(&func_id) = maps.fn_by_addr.get(&addr) else {
let resolve_id = |raw: &str| -> Option<u64> {
let clean = strip_r2_prefix(raw);
let clean = clean.strip_prefix("imp.").unwrap_or(&clean);
name_to_id.get(clean).copied()
};

for node in &nodes {
let Some(raw) = node.name.as_deref() else {
continue;
};
let mut chain = vec![func_id];
for &to in by_caller.get(&addr).into_iter().flat_map(|v| v.iter()) {
let call_name = resolve_call_name(to, maps);
let Some(caller_id) = resolve_id(raw) else {
continue;
};
if caller_id == 0 {
continue;
}
let mut chain = vec![caller_id];
for callee in node.imports.iter().flatten() {
let clean = strip_r2_prefix(callee);
let clean = clean.strip_prefix("imp.").unwrap_or(&clean).to_string();
let pos = chain.len();
chain.push(0);
calls.push(CallRecord {
caller_id: func_id,
call_name,
caller_id,
call_name: clean,
position: pos,
arg_exprs: Vec::new(),
line: addr.try_into().unwrap_or(0),
line: 0,
condition: None,
is_loop_body: false,
effect: EffectType::None,
Expand All @@ -383,7 +402,7 @@ fn build_chains_from_graph(
target_method: None,
});
}
chains.insert(func_id, chain);
chains.insert(caller_id, chain);
}
Ok(())
}
Expand All @@ -408,17 +427,3 @@ fn resolve_call_target(target: Option<u64>, maps: &FnMaps) -> (u64, String) {
}
(0, format!("sub_{addr:x}"))
}

fn resolve_call_name(addr: u64, maps: &FnMaps) -> String {
if let Some(name) = maps.plt_by_addr.get(&addr) {
return name.clone();
}
if let Some(&fid) = maps.fn_by_addr.get(&addr) {
return maps
.fn_id_to_name
.get(&fid)
.cloned()
.unwrap_or_else(|| format!("sub_{addr:x}"));
}
format!("sub_{addr:x}")
}
18 changes: 12 additions & 6 deletions crates/codegraph-binary/src/model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,9 @@ pub struct BinMeta {
/// Danh sách function từ `aflj`.
#[derive(Debug, Deserialize)]
pub struct FnEntry {
pub offset: Option<u64>,
/// r2 6.x trả `addr`; bản cũ trả `offset`.
#[serde(alias = "offset")]
pub addr: Option<u64>,
pub name: Option<String>,
pub size: Option<u64>,
pub realsz: Option<u64>,
Expand Down Expand Up @@ -68,6 +70,8 @@ pub struct Xref {
/// Entry import từ `iij`.
#[derive(Debug, Deserialize)]
pub struct ImportEntry {
/// r2 6.x trả `name`; bản cũ trả `import`.
#[serde(default, rename = "name", alias = "import")]
pub import: Option<String>,
pub ordinal: Option<u64>,
pub bind: Option<String>,
Expand Down Expand Up @@ -104,17 +108,19 @@ pub struct StrEntry {
pub string: Option<String>,
}

/// Call graph edge từ `agCj`.
/// Node call graph từ `agCj` (r2 6.x): mỗi function kèm danh sách callee theo tên.
#[derive(Debug, Deserialize)]
pub struct CallGraphEdge {
pub from: Option<u64>,
pub to: Option<u64>,
pub struct CallGraphNode {
pub name: Option<String>,
pub imports: Option<Vec<String>>,
}

/// Một lệnh disasm trong `pdfj.ops`.
#[derive(Debug, Deserialize)]
pub struct DisasmOp {
pub offset: Option<u64>,
/// r2 6.x trả `addr`; bản cũ trả `offset`.
#[serde(alias = "offset")]
pub addr: Option<u64>,
pub size: Option<u64>,
pub esil: Option<String>,
pub bytes: Option<String>,
Expand Down
13 changes: 12 additions & 1 deletion crates/codegraph-binary/src/r2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,18 @@ impl R2Session {
.ok_or_else(|| Error::Parse(format!("path không phải UTF-8: {}", path.display())))?;
let opts = R2PipeSpawnOptions {
exepath: "r2".to_string(),
args: vec!["-N", "-e", "scr.color=0", "-e", "scr.utf8=0"],
// bin.relocs.apply=true: với shared lib (ELF .so), relocations phải
// được apply trước khi phân tích, nếu không nhiều function resolve
// về địa chỉ 0 và `pdfj @ 0` fail ("Cannot find function at 0x0").
args: vec![
"-N",
"-e",
"scr.color=0",
"-e",
"scr.utf8=0",
"-e",
"bin.relocs.apply=true",
],
};
let inner = R2Pipe::spawn(path_str, Some(opts))
.map_err(|e| Error::Parse(format!("không thể spawn r2 cho {}: {e}. Hãy cài radare2: brew install radare2 / apt install radare2", path.display())))?;
Expand Down
25 changes: 12 additions & 13 deletions crates/codegraph-binary/tests/extract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@ impl MockR2 {
responses.insert(
"aflj".to_string(),
json!([
{"offset": 4198496, "name": "main", "size": 64, "cc": 1.0, "calltype": "cdecl"},
{"offset": 4198560, "name": "fcn.00401160", "size": 32, "cc": 2.0},
{"offset": 4196112, "name": "sym.imp.LIBC.so.6_puts", "size": 16}
{"addr": 4198496, "name": "main", "size": 64, "cc": 1.0, "calltype": "cdecl"},
{"addr": 4198560, "name": "fcn.00401160", "size": 32, "cc": 2.0},
{"addr": 4196112, "name": "sym.imp.LIBC.so.6_puts", "size": 16}
]),
);
// iij: 1 import puts
Expand All @@ -41,22 +41,21 @@ impl MockR2 {
// agCj: main → helper, main → puts(plt)
responses.insert(
"agCj".to_string(),
json!({"edges": [
{"from": 4198496, "to": 4198560},
{"from": 4198496, "to": 4196112}
]}),
json!([
{"name": "main", "size": 64, "imports": ["fcn.00401160", "sym.imp.puts"]}
]),
);
// pdfj main: call + return + branch
responses.insert(
"pdfj @ 4198496".to_string(),
json!({
"name": "main", "offset": 4198496, "size": 64,
"name": "main", "addr": 4198496, "size": 64,
"ops": [
{"offset": 4198496, "type": "push", "disasm": "push rbp"},
{"offset": 4198500, "type": "cjmp", "jump": 4198520, "fail": 4198512, "disasm": "je 0x401018"},
{"offset": 4198504, "type": "call", "jump": 4196112, "disasm": "call sym.imp.LIBC.so.6_puts"},
{"offset": 4198510, "type": "jmp", "jump": 4198496, "disasm": "jmp 0x401000"},
{"offset": 4198560, "type": "ret", "disasm": "ret"}
{"addr": 4198496, "type": "push", "disasm": "push rbp"},
{"addr": 4198500, "type": "cjmp", "jump": 4198520, "fail": 4198512, "disasm": "je 0x401018"},
{"addr": 4198504, "type": "call", "jump": 4196112, "disasm": "call sym.imp.LIBC.so.6_puts"},
{"addr": 4198510, "type": "jmp", "jump": 4198496, "disasm": "jmp 0x401000"},
{"addr": 4198560, "type": "ret", "disasm": "ret"}
]
}),
);
Expand Down
Loading
Loading