Skip to content

Commit b991960

Browse files
AetiasHaxencounter
andauthored
ARMv5TE (DS) support (#68)
* Initial ARM support * Disassemble const pool reloc * Disasm ARM/Thumb/data based on mapping symbols * Fallback to mapping symbol `$a` * Support multiple DWARF sequences * Update line info * Rework DWARF line info parsing - Properly handles multiple sections in DWARF 1 - line_info moved into ObjSection - DWARF 2 parser no longer errors with no .text section - Both parsers properly skip empty sections * Simplify line_info (no Option) * Get line info from section; output formatted ins string * Unwrap code section in `arm.rs` * Handle reloc `R_ARM_SBREL32` * Update ARM disassembler * Update README.md * Format * Revert "Update README.md" This reverts commit 8bbfcc6. * Update README.md --------- Co-authored-by: Luke Street <[email protected]>
1 parent 425dc85 commit b991960

File tree

5 files changed

+314
-1
lines changed

5 files changed

+314
-1
lines changed

Cargo.lock

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

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ Supports:
1717
- PowerPC 750CL (GameCube, Wii)
1818
- MIPS (N64, PS1, PS2, PSP)
1919
- x86 (COFF only at the moment)
20+
- ARM (DS)
2021

2122
See [Usage](#usage) for more information.
2223

objdiff-core/Cargo.toml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,14 @@ A local diffing tool for decompilation projects.
1212
"""
1313

1414
[features]
15-
all = ["config", "dwarf", "mips", "ppc", "x86"]
15+
all = ["config", "dwarf", "mips", "ppc", "x86", "arm"]
1616
any-arch = [] # Implicit, used to check if any arch is enabled
1717
config = ["globset", "semver", "serde_json", "serde_yaml"]
1818
dwarf = ["gimli"]
1919
mips = ["any-arch", "rabbitizer"]
2020
ppc = ["any-arch", "cwdemangle", "ppc750cl"]
2121
x86 = ["any-arch", "cpp_demangle", "iced-x86", "msvc-demangler"]
22+
arm = ["any-arch", "cpp_demangle", "unarm"]
2223

2324
[dependencies]
2425
anyhow = "1.0.82"
@@ -53,3 +54,6 @@ rabbitizer = { version = "1.11.0", optional = true }
5354
cpp_demangle = { version = "0.4.3", optional = true }
5455
iced-x86 = { version = "1.21.0", default-features = false, features = ["std", "decoder", "intel", "gas", "masm", "nasm", "exhaustive_enums"], optional = true }
5556
msvc-demangler = { version = "0.10.0", optional = true }
57+
58+
# arm
59+
unarm = { version = "1.0.0", optional = true }

objdiff-core/src/arch/arm.rs

Lines changed: 297 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,297 @@
1+
use std::{borrow::Cow, collections::HashMap};
2+
3+
use anyhow::{anyhow, bail, Result};
4+
use object::{
5+
elf, File, Object, ObjectSection, ObjectSymbol, Relocation, RelocationFlags, SectionIndex,
6+
SectionKind, Symbol,
7+
};
8+
use unarm::{
9+
args::{Argument, OffsetImm, OffsetReg, Register},
10+
parse::{ArmVersion, ParseMode, Parser},
11+
ParsedIns,
12+
};
13+
14+
use crate::{
15+
arch::{ObjArch, ProcessCodeResult},
16+
diff::DiffObjConfig,
17+
obj::{ObjInfo, ObjIns, ObjInsArg, ObjInsArgValue, ObjSection, SymbolRef},
18+
};
19+
20+
pub struct ObjArchArm {
21+
/// Maps section index, to list of disasm modes (arm, thumb or data) sorted by address
22+
disasm_modes: HashMap<SectionIndex, Vec<DisasmMode>>,
23+
}
24+
25+
impl ObjArchArm {
26+
pub fn new(file: &File) -> Result<Self> {
27+
match file {
28+
File::Elf32(_) => {
29+
let disasm_modes: HashMap<_, _> = file
30+
.sections()
31+
.filter(|s| s.kind() == SectionKind::Text)
32+
.map(|s| {
33+
let index = s.index();
34+
let mut mapping_symbols: Vec<_> = file
35+
.symbols()
36+
.filter(|s| s.section_index().map(|i| i == index).unwrap_or(false))
37+
.filter_map(|s| DisasmMode::from_symbol(&s))
38+
.collect();
39+
mapping_symbols.sort_unstable_by_key(|x| x.address);
40+
(s.index(), mapping_symbols)
41+
})
42+
.collect();
43+
Ok(Self { disasm_modes })
44+
}
45+
_ => bail!("Unsupported file format {:?}", file.format()),
46+
}
47+
}
48+
}
49+
50+
impl ObjArch for ObjArchArm {
51+
fn process_code(
52+
&self,
53+
obj: &ObjInfo,
54+
symbol_ref: SymbolRef,
55+
config: &DiffObjConfig,
56+
) -> Result<ProcessCodeResult> {
57+
let (section, symbol) = obj.section_symbol(symbol_ref);
58+
let section = section.ok_or_else(|| anyhow!("Code symbol section not found"))?;
59+
let code = &section.data
60+
[symbol.section_address as usize..(symbol.section_address + symbol.size) as usize];
61+
62+
let start_addr = symbol.address as u32;
63+
let end_addr = start_addr + symbol.size as u32;
64+
65+
// Mapping symbols decide what kind of data comes after it. $a for ARM code, $t for Thumb code and $d for data.
66+
let fallback_mappings =
67+
[DisasmMode { address: symbol.address as u32, mapping: ParseMode::Arm }];
68+
let mapping_symbols = self
69+
.disasm_modes
70+
.get(&SectionIndex(section.orig_index))
71+
.map(|x| x.as_slice())
72+
.unwrap_or(&fallback_mappings);
73+
let first_mapping_idx =
74+
match mapping_symbols.binary_search_by_key(&(symbol.address as u32), |x| x.address) {
75+
Ok(idx) => idx,
76+
Err(idx) => idx - 1,
77+
};
78+
let first_mapping = mapping_symbols[first_mapping_idx].mapping;
79+
80+
let mut mappings_iter =
81+
mapping_symbols.iter().skip(first_mapping_idx + 1).take_while(|x| x.address < end_addr);
82+
let mut next_mapping = mappings_iter.next();
83+
84+
let ins_count = code.len() / first_mapping.instruction_size();
85+
let mut ops = Vec::<u16>::with_capacity(ins_count);
86+
let mut insts = Vec::<ObjIns>::with_capacity(ins_count);
87+
let mut parser = Parser::new(ArmVersion::V5Te, first_mapping, start_addr, code);
88+
89+
while let Some((address, op, ins)) = parser.next() {
90+
if let Some(next) = next_mapping {
91+
let next_address = parser.address;
92+
if next_address >= next.address {
93+
// Change mapping
94+
parser.mode = next.mapping;
95+
next_mapping = mappings_iter.next();
96+
}
97+
}
98+
99+
let line = section.line_info.range(..=address as u64).last().map(|(_, &b)| b);
100+
101+
let reloc =
102+
section.relocations.iter().find(|r| (r.address as u32 & !1) == address).cloned();
103+
104+
let mut reloc_arg = None;
105+
if let Some(reloc) = &reloc {
106+
match reloc.flags {
107+
RelocationFlags::Elf { r_type: elf::R_ARM_THM_XPC22 }
108+
| RelocationFlags::Elf { r_type: elf::R_ARM_PC24 } => {
109+
reloc_arg =
110+
ins.args.iter().rposition(|a| matches!(a, Argument::BranchDest(_)));
111+
}
112+
_ => (),
113+
}
114+
};
115+
116+
let (args, branch_dest) = if reloc.is_some() && parser.mode == ParseMode::Data {
117+
(vec![ObjInsArg::Reloc], None)
118+
} else {
119+
push_args(&ins, config, reloc_arg, address)?
120+
};
121+
122+
ops.push(op.id());
123+
insts.push(ObjIns {
124+
address: address as u64,
125+
size: (parser.address - address) as u8,
126+
op: op.id(),
127+
mnemonic: ins.mnemonic.to_string(),
128+
args,
129+
reloc,
130+
branch_dest,
131+
line,
132+
formatted: ins.to_string(),
133+
orig: None,
134+
});
135+
}
136+
137+
Ok(ProcessCodeResult { ops, insts })
138+
}
139+
140+
fn implcit_addend(
141+
&self,
142+
_section: &ObjSection,
143+
address: u64,
144+
reloc: &Relocation,
145+
) -> anyhow::Result<i64> {
146+
bail!("Unsupported ARM implicit relocation {:#x}{:?}", address, reloc.flags())
147+
}
148+
149+
fn demangle(&self, name: &str) -> Option<String> {
150+
cpp_demangle::Symbol::new(name)
151+
.ok()
152+
.and_then(|s| s.demangle(&cpp_demangle::DemangleOptions::default()).ok())
153+
}
154+
155+
fn display_reloc(&self, flags: RelocationFlags) -> Cow<'static, str> {
156+
Cow::Owned(format!("<{flags:?}>"))
157+
}
158+
}
159+
160+
#[derive(Clone, Copy, Debug)]
161+
struct DisasmMode {
162+
address: u32,
163+
mapping: ParseMode,
164+
}
165+
166+
impl DisasmMode {
167+
fn from_symbol<'a>(sym: &Symbol<'a, '_, &'a [u8]>) -> Option<Self> {
168+
if let Ok(name) = sym.name() {
169+
ParseMode::from_mapping_symbol(name)
170+
.map(|mapping| DisasmMode { address: sym.address() as u32, mapping })
171+
} else {
172+
None
173+
}
174+
}
175+
}
176+
177+
fn push_args(
178+
parsed_ins: &ParsedIns,
179+
config: &DiffObjConfig,
180+
reloc_arg: Option<usize>,
181+
cur_addr: u32,
182+
) -> Result<(Vec<ObjInsArg>, Option<u64>)> {
183+
let mut args = vec![];
184+
let mut branch_dest = None;
185+
let mut writeback = false;
186+
let mut deref = false;
187+
for (i, arg) in parsed_ins.args_iter().enumerate() {
188+
// Emit punctuation before separator
189+
if deref {
190+
match arg {
191+
Argument::OffsetImm(OffsetImm { post_indexed: true, value: _ })
192+
| Argument::OffsetReg(OffsetReg { add: _, post_indexed: true, reg: _ })
193+
| Argument::CoOption(_) => {
194+
deref = false;
195+
args.push(ObjInsArg::PlainText("]".into()));
196+
if writeback {
197+
writeback = false;
198+
args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque("!".into())));
199+
}
200+
}
201+
_ => {}
202+
}
203+
}
204+
205+
if i > 0 {
206+
args.push(ObjInsArg::PlainText(config.separator().into()));
207+
}
208+
209+
if reloc_arg == Some(i) {
210+
args.push(ObjInsArg::Reloc);
211+
} else {
212+
match arg {
213+
Argument::Reg(reg) => {
214+
if reg.deref {
215+
deref = true;
216+
args.push(ObjInsArg::PlainText("[".into()));
217+
}
218+
args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque(reg.reg.to_string().into())));
219+
if reg.writeback {
220+
if reg.deref {
221+
writeback = true;
222+
} else {
223+
args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque("!".into())));
224+
}
225+
}
226+
}
227+
Argument::RegList(reg_list) => {
228+
args.push(ObjInsArg::PlainText("{".into()));
229+
let mut first = true;
230+
for i in 0..16 {
231+
if (reg_list.regs & (1 << i)) != 0 {
232+
if !first {
233+
args.push(ObjInsArg::PlainText(config.separator().into()));
234+
}
235+
args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque(
236+
Register::parse(i).to_string().into(),
237+
)));
238+
first = false;
239+
}
240+
}
241+
args.push(ObjInsArg::PlainText("}".into()));
242+
if reg_list.user_mode {
243+
args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque("^".to_string().into())));
244+
}
245+
}
246+
Argument::UImm(value) | Argument::CoOpcode(value) => {
247+
args.push(ObjInsArg::PlainText("#".into()));
248+
args.push(ObjInsArg::Arg(ObjInsArgValue::Unsigned(*value as u64)));
249+
}
250+
Argument::SImm(value)
251+
| Argument::OffsetImm(OffsetImm { post_indexed: _, value }) => {
252+
args.push(ObjInsArg::PlainText("#".into()));
253+
args.push(ObjInsArg::Arg(ObjInsArgValue::Signed(*value as i64)));
254+
}
255+
Argument::BranchDest(value) => {
256+
let dest = cur_addr.wrapping_add_signed(*value) as u64;
257+
args.push(ObjInsArg::BranchDest(dest));
258+
branch_dest = Some(dest);
259+
}
260+
Argument::CoOption(value) => {
261+
args.push(ObjInsArg::PlainText("{".into()));
262+
args.push(ObjInsArg::Arg(ObjInsArgValue::Unsigned(*value as u64)));
263+
args.push(ObjInsArg::PlainText("}".into()));
264+
}
265+
Argument::CoprocNum(value) => {
266+
args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque(format!("p{}", value).into())));
267+
}
268+
Argument::ShiftImm(shift) => {
269+
args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque(shift.op.to_string().into())));
270+
args.push(ObjInsArg::PlainText(" #".into()));
271+
args.push(ObjInsArg::Arg(ObjInsArgValue::Unsigned(shift.imm as u64)));
272+
}
273+
Argument::ShiftReg(shift) => {
274+
args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque(shift.op.to_string().into())));
275+
args.push(ObjInsArg::PlainText(" ".into()));
276+
args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque(shift.reg.to_string().into())));
277+
}
278+
Argument::OffsetReg(offset) => {
279+
if !offset.add {
280+
args.push(ObjInsArg::PlainText("-".into()));
281+
}
282+
args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque(
283+
offset.reg.to_string().into(),
284+
)));
285+
}
286+
_ => args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque(arg.to_string().into()))),
287+
}
288+
}
289+
}
290+
if deref {
291+
args.push(ObjInsArg::PlainText("]".into()));
292+
if writeback {
293+
args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque("!".into())));
294+
}
295+
}
296+
Ok((args, branch_dest))
297+
}

objdiff-core/src/arch/mod.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ use crate::{
88
obj::{ObjIns, ObjReloc, ObjSection},
99
};
1010

11+
#[cfg(feature = "arm")]
12+
mod arm;
1113
#[cfg(feature = "mips")]
1214
pub mod mips;
1315
#[cfg(feature = "ppc")]
@@ -46,6 +48,8 @@ pub fn new_arch(object: &object::File) -> Result<Box<dyn ObjArch>> {
4648
Architecture::Mips => Box::new(mips::ObjArchMips::new(object)?),
4749
#[cfg(feature = "x86")]
4850
Architecture::I386 | Architecture::X86_64 => Box::new(x86::ObjArchX86::new(object)?),
51+
#[cfg(feature = "arm")]
52+
Architecture::Arm => Box::new(arm::ObjArchArm::new(object)?),
4953
arch => bail!("Unsupported architecture: {arch:?}"),
5054
})
5155
}

0 commit comments

Comments
 (0)