diff --git a/Cargo.lock b/Cargo.lock index 163f2f587..11c860c3f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -720,7 +720,7 @@ checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" [[package]] name = "codegraph" -version = "2.1.0" +version = "2.1.1" dependencies = [ "anyhow", "camino", @@ -741,7 +741,7 @@ dependencies = [ [[package]] name = "codegraph-api" -version = "2.1.0" +version = "2.1.1" dependencies = [ "anyhow", "camino", @@ -758,7 +758,7 @@ dependencies = [ [[package]] name = "codegraph-bench" -version = "2.1.0" +version = "2.1.1" dependencies = [ "anyhow", "camino", @@ -776,7 +776,7 @@ dependencies = [ [[package]] name = "codegraph-binary" -version = "2.1.0" +version = "2.1.1" dependencies = [ "camino", "codegraph-core", @@ -792,7 +792,7 @@ dependencies = [ [[package]] name = "codegraph-context" -version = "2.1.0" +version = "2.1.1" dependencies = [ "codegraph-core", "codegraph-graph", @@ -804,7 +804,7 @@ dependencies = [ [[package]] name = "codegraph-core" -version = "2.1.0" +version = "2.1.1" dependencies = [ "async-graphql", "camino", @@ -815,7 +815,7 @@ dependencies = [ [[package]] name = "codegraph-extract" -version = "2.1.0" +version = "2.1.1" dependencies = [ "camino", "codegraph-binary", @@ -850,7 +850,7 @@ dependencies = [ [[package]] name = "codegraph-graph" -version = "2.1.0" +version = "2.1.1" dependencies = [ "async-trait", "bincode", @@ -880,7 +880,7 @@ dependencies = [ [[package]] name = "codegraph-graphql" -version = "2.1.0" +version = "2.1.1" dependencies = [ "anyhow", "async-graphql", @@ -902,7 +902,7 @@ dependencies = [ [[package]] name = "codegraph-installer" -version = "2.1.0" +version = "2.1.1" dependencies = [ "anyhow", "camino", @@ -918,7 +918,7 @@ dependencies = [ [[package]] name = "codegraph-mcp" -version = "2.1.0" +version = "2.1.1" dependencies = [ "anyhow", "axum", @@ -940,7 +940,7 @@ dependencies = [ [[package]] name = "codegraph-sboxes" -version = "2.1.0" +version = "2.1.1" dependencies = [ "camino", "codegraph-core", diff --git a/Cargo.toml b/Cargo.toml index 4d0c4e064..468db4894 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,7 +16,7 @@ members = [ ] [workspace.package] -version = "2.1.0" +version = "2.1.1" edition = "2021" rust-version = "1.80" license = "MIT" diff --git a/crates/codegraph-binary/src/extract.rs b/crates/codegraph-binary/src/extract.rs index 27299be13..88e7a670e 100644 --- a/crates/codegraph-binary/src/extract.rs +++ b/crates/codegraph-binary/src/extract.rs @@ -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() @@ -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) @@ -267,12 +267,25 @@ fn build_chains_with_cfg( calls: &mut Vec, ) -> 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 = 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 = ops_json .get("ops") .and_then(|o| o.as_array()) .cloned() @@ -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() { @@ -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>, calls: &mut Vec, ) -> Result<(), Error> { - let edges: Vec = session - .cmdj("agCj")? - .get("edges") - .and_then(|e| e.as_array()) - .cloned() - .unwrap_or_default() - .into_iter() - .filter_map(|v| serde_json::from_value::(v).ok()) - .collect(); + let nodes: Vec = parse_array(session.cmdj("agCj")?)?; - let mut by_caller: HashMap> = 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 = 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 { + 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, @@ -383,7 +402,7 @@ fn build_chains_from_graph( target_method: None, }); } - chains.insert(func_id, chain); + chains.insert(caller_id, chain); } Ok(()) } @@ -408,17 +427,3 @@ fn resolve_call_target(target: Option, 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}") -} diff --git a/crates/codegraph-binary/src/model.rs b/crates/codegraph-binary/src/model.rs index 262db4ff7..3eb5e92b2 100644 --- a/crates/codegraph-binary/src/model.rs +++ b/crates/codegraph-binary/src/model.rs @@ -36,7 +36,9 @@ pub struct BinMeta { /// Danh sách function từ `aflj`. #[derive(Debug, Deserialize)] pub struct FnEntry { - pub offset: Option, + /// r2 6.x trả `addr`; bản cũ trả `offset`. + #[serde(alias = "offset")] + pub addr: Option, pub name: Option, pub size: Option, pub realsz: Option, @@ -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, pub ordinal: Option, pub bind: Option, @@ -104,17 +108,19 @@ pub struct StrEntry { pub string: Option, } -/// 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, - pub to: Option, +pub struct CallGraphNode { + pub name: Option, + pub imports: Option>, } /// Một lệnh disasm trong `pdfj.ops`. #[derive(Debug, Deserialize)] pub struct DisasmOp { - pub offset: Option, + /// r2 6.x trả `addr`; bản cũ trả `offset`. + #[serde(alias = "offset")] + pub addr: Option, pub size: Option, pub esil: Option, pub bytes: Option, diff --git a/crates/codegraph-binary/src/r2.rs b/crates/codegraph-binary/src/r2.rs index 952a90b7a..09ec6dff9 100644 --- a/crates/codegraph-binary/src/r2.rs +++ b/crates/codegraph-binary/src/r2.rs @@ -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())))?; diff --git a/crates/codegraph-binary/tests/extract.rs b/crates/codegraph-binary/tests/extract.rs index c37d8cffc..475ef008b 100644 --- a/crates/codegraph-binary/tests/extract.rs +++ b/crates/codegraph-binary/tests/extract.rs @@ -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 @@ -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"} ] }), ); diff --git a/crates/codegraph/src/main.rs b/crates/codegraph/src/main.rs index dc821f83a..0bad09945 100644 --- a/crates/codegraph/src/main.rs +++ b/crates/codegraph/src/main.rs @@ -347,8 +347,11 @@ async fn cmd_doctor(root: &Utf8Path) -> Result<()> { let tools: Vec<&str> = vec!["git", "tar", "r2"]; println!("Tools on PATH :"); for t in tools { + // radare2 doesn't support `--version` (it parses it as a file to open); + // fall back to `-v` when `--version` fails. + let version_flag = if t == "r2" { "-v" } else { "--version" }; let ok = std::process::Command::new(t) - .arg("--version") + .arg(version_flag) .status() .map(|s| s.success()) .unwrap_or(false); diff --git a/packaging/aur/codegraph-rs-bin/PKGBUILD b/packaging/aur/codegraph-rs-bin/PKGBUILD index ea1a76960..5f9ed5c3d 100644 --- a/packaging/aur/codegraph-rs-bin/PKGBUILD +++ b/packaging/aur/codegraph-rs-bin/PKGBUILD @@ -1,6 +1,6 @@ # Maintainer: Hung Pham pkgname=codegraph-rs-bin -pkgver=2.1.0 +pkgver=2.1.1 pkgrel=1 pkgdesc="Local-first code intelligence: tree-sitter knowledge graph + MCP server (prebuilt binary)" arch=('x86_64' 'aarch64') diff --git a/packaging/choco/codegraph.nuspec b/packaging/choco/codegraph.nuspec index a1a100147..5ad9c5f4a 100644 --- a/packaging/choco/codegraph.nuspec +++ b/packaging/choco/codegraph.nuspec @@ -2,7 +2,7 @@ codegraph - 2.1.0 + 2.1.1 codegraph Hung Pham https://github.com/hungpham10/codegraph-rs diff --git a/packaging/winget/codegraph.yaml b/packaging/winget/codegraph.yaml index 2cfd83a68..c6db84e22 100644 --- a/packaging/winget/codegraph.yaml +++ b/packaging/winget/codegraph.yaml @@ -6,7 +6,7 @@ # release time (or automate it in the release pipeline before submitting to # microsoft/winget-pkgs). PackageIdentifier: hungpham10.codegraph -PackageVersion: 2.1.0 +PackageVersion: 2.1.1 PackageName: codegraph Publisher: Hung Pham PublisherUrl: https://github.com/hungpham10/codegraph-rs @@ -18,7 +18,7 @@ PackageUrl: https://github.com/hungpham10/codegraph-rs InstallerType: zip Installers: - Architecture: x64 - InstallerUrl: https://github.com/hungpham10/codegraph-rs/releases/download/v2.1.0/codegraph-x86_64-pc-windows-msvc.zip + InstallerUrl: https://github.com/hungpham10/codegraph-rs/releases/download/v2.1.1/codegraph-x86_64-pc-windows-msvc.zip InstallerSha256: 0000000000000000000000000000000000000000000000000000000000000000 InstallerType: zip ManifestType: singleton diff --git a/scripts/install.ps1 b/scripts/install.ps1 index 4ad2b2b43..2313780b7 100644 --- a/scripts/install.ps1 +++ b/scripts/install.ps1 @@ -5,11 +5,11 @@ # # Usage (pin a version / download the script first): # irm https://raw.githubusercontent.com/hungpham10/codegraph-rs/main/scripts/install.ps1 -OutFile install.ps1 -# .\install.ps1 -Version 2.1.0 +# .\install.ps1 -Version 2.1.1 [CmdletBinding()] param( - # Pin a specific version, e.g. "2.1.0". Empty = latest release. + # Pin a specific version, e.g. "2.1.1". Empty = latest release. [string]$Version )