Skip to main content

ds_decomp/analysis/
functions.rs

1use std::{
2    backtrace::Backtrace,
3    collections::{BTreeMap, BTreeSet},
4    fmt::{Display, Formatter},
5};
6
7use snafu::Snafu;
8use unarm::{
9    ArmVersion, Endian, Ins, ParseFlags, ParseMode, ParsedIns, Parser,
10    args::{Argument, Reg, Register},
11    arm, thumb,
12};
13
14use super::{
15    function_branch::FunctionBranchState,
16    function_start::is_valid_function_start,
17    illegal_code::IllegalCodeState,
18    inline_table::{InlineTable, InlineTableState},
19    jump_table::{JumpTable, JumpTableState},
20    secure_area::SecureAreaState,
21};
22use crate::{
23    config::symbol::{SymbolMap, SymbolMapError},
24    util::bytes::FromSlice,
25};
26
27// All keys in the types below are instruction addresses
28pub type Labels = BTreeSet<u32>;
29pub type PoolConstants = BTreeSet<u32>;
30pub type JumpTables = BTreeMap<u32, JumpTable>;
31pub type InlineTables = BTreeMap<u32, InlineTable>;
32pub type FunctionCalls = BTreeMap<u32, CalledFunction>;
33pub type DataLoads = BTreeMap<u32, u32>;
34
35#[derive(Debug, Clone)]
36pub struct Function {
37    name: String,
38    start_address: u32,
39    end_address: u32,
40    first_instruction_address: u32,
41    thumb: bool,
42    labels: Labels,
43    pool_constants: PoolConstants,
44    jump_tables: JumpTables,
45    inline_tables: InlineTables,
46    function_calls: FunctionCalls,
47}
48
49#[derive(Debug, Snafu)]
50pub enum FunctionAnalysisError {
51    #[snafu(transparent)]
52    IntoFunction { source: IntoFunctionError },
53    #[snafu(transparent)]
54    SymbolMap { source: SymbolMapError },
55}
56
57const PARSE_FLAGS: ParseFlags = ParseFlags { version: ArmVersion::V5Te, ual: false };
58
59impl Function {
60    pub fn size(&self) -> u32 {
61        self.end_address - self.first_instruction_address
62    }
63
64    fn is_thumb_function(address: u32, code: &[u8]) -> bool {
65        if (address & 3) != 0 {
66            // Not 4-aligned, must be Thumb
67            true
68        } else if code.len() < 4 {
69            // Can't contain a full ARM instruction
70            true
71        } else if code[3] & 0xf0 == 0xe0 {
72            // First instruction has the AL condition code, must be ARM
73            false
74        } else {
75            // Thumb otherwise
76            true
77        }
78    }
79
80    #[allow(clippy::match_like_matches_macro)]
81    fn is_entry_instruction(ins: Ins, parsed_ins: &ParsedIns) -> bool {
82        if ins.is_conditional() {
83            return false;
84        }
85
86        let args = &parsed_ins.args;
87        match (parsed_ins.mnemonic, args[0], args[1], args[2]) {
88            (
89                "stmdb",
90                Argument::Reg(Reg { reg: Register::Sp, writeback: true, deref: false }),
91                Argument::RegList(regs),
92                Argument::None,
93            )
94            | ("push", Argument::RegList(regs), Argument::None, Argument::None)
95                if regs.contains(Register::Lr) =>
96            {
97                true
98            }
99            _ => false,
100        }
101    }
102
103    fn is_branch(ins: Ins, parsed_ins: &ParsedIns, address: u32) -> Option<u32> {
104        if ins.mnemonic() != "b" {
105            return None;
106        }
107        let dest = parsed_ins.branch_destination().unwrap();
108        Some((address as i32 + dest).try_into().unwrap())
109    }
110
111    fn is_pool_load(ins: Ins, parsed_ins: &ParsedIns, address: u32, thumb: bool) -> Option<u32> {
112        if ins.mnemonic() != "ldr" {
113            return None;
114        }
115        match (parsed_ins.args[0], parsed_ins.args[1], parsed_ins.args[2]) {
116            (Argument::Reg(dest), Argument::Reg(base), Argument::OffsetImm(offset)) => {
117                if dest.reg == Register::Pc {
118                    None
119                } else if !base.deref || base.reg != Register::Pc {
120                    None
121                } else if offset.post_indexed {
122                    None
123                } else {
124                    // ldr *, [pc + *]
125                    let load_address = (address as i32 + offset.value) as u32 & !3;
126                    let load_address = load_address + if thumb { 4 } else { 8 };
127                    Some(load_address)
128                }
129            }
130            _ => None,
131        }
132    }
133
134    fn is_function_call(
135        ins: Ins,
136        parsed_ins: &ParsedIns,
137        address: u32,
138        thumb: bool,
139    ) -> Option<CalledFunction> {
140        let args = &parsed_ins.args;
141        match (ins.mnemonic(), args[0], args[1]) {
142            ("bl", Argument::BranchDest(offset), Argument::None) => {
143                let destination = (address as i32 + offset) as u32;
144                Some(CalledFunction { ins, address: destination, thumb })
145            }
146            ("blx", Argument::BranchDest(offset), Argument::None) => {
147                let destination = (address as i32 + offset) as u32;
148                let destination = if thumb { destination & !3 } else { destination };
149                Some(CalledFunction { ins, address: destination, thumb: !thumb })
150            }
151            _ => None,
152        }
153    }
154
155    fn function_parser_loop(
156        mut parser: Parser<'_>,
157        options: FunctionParseOptions,
158    ) -> Result<Function, FunctionAnalysisError> {
159        let thumb = parser.mode == ParseMode::Thumb;
160        let mut context = ParseFunctionContext::new(thumb, options);
161
162        let Some((address, ins, parsed_ins)) = parser.next() else {
163            return Err(FunctionAnalysisError::IntoFunction {
164                source: NoEpilogueSnafu.build().into(),
165            });
166        };
167        if !is_valid_function_start(address, ins, &parsed_ins) {
168            return Err(FunctionAnalysisError::IntoFunction {
169                source: InvalidStartSnafu { address, ins }.build().into(),
170            });
171        }
172
173        let state = context.handle_ins(&mut parser, address, ins, parsed_ins);
174        let mut function = if state.ended() {
175            return Ok(context.into_function(state)?);
176        } else {
177            loop {
178                let Some((address, ins, parsed_ins)) = parser.next() else {
179                    break context.into_function(ParseFunctionState::Done)?;
180                };
181                let state = context.handle_ins(&mut parser, address, ins, parsed_ins);
182                if state.ended() {
183                    break context.into_function(state)?;
184                }
185            }
186        };
187
188        if let Some(first_pool_address) = function.pool_constants.first()
189            && *first_pool_address < function.start_address
190        {
191            log::info!(
192                "Function at {:#010x} was adjusted to include pre-code constant pool at {:#010x}",
193                function.start_address,
194                first_pool_address
195            );
196
197            function.first_instruction_address = function.start_address;
198            function.start_address = *first_pool_address;
199        }
200
201        Ok(function)
202    }
203
204    pub fn parse_function(
205        options: FunctionParseOptions,
206    ) -> Result<Function, FunctionAnalysisError> {
207        let FunctionParseOptions {
208            start_address, base_address, module_code, parse_options, ..
209        } = &options;
210
211        let thumb =
212            parse_options.thumb.unwrap_or(Function::is_thumb_function(*start_address, module_code));
213        let parse_mode = if thumb { ParseMode::Thumb } else { ParseMode::Arm };
214        let start = (start_address - base_address) as usize;
215        let function_code = &module_code[start..];
216        let parser =
217            Parser::new(parse_mode, *start_address, Endian::Little, PARSE_FLAGS, function_code);
218
219        Self::function_parser_loop(parser, options)
220    }
221
222    pub fn find_functions(
223        options: FindFunctionsOptions,
224    ) -> Result<BTreeMap<u32, Function>, FunctionAnalysisError> {
225        let FindFunctionsOptions {
226            default_name_prefix,
227            base_address,
228            module_code,
229            symbol_map,
230            module_start_address,
231            module_end_address,
232            search_options,
233        } = options;
234
235        let mut functions = BTreeMap::new();
236
237        let start_address = search_options.start_address.unwrap_or(base_address);
238        assert!((start_address & 1) == 0);
239        let start_offset = start_address - base_address;
240        let end_address =
241            search_options.end_address.unwrap_or(base_address + module_code.len() as u32);
242        let end_offset = end_address - base_address;
243        let module_code = &module_code[..end_offset as usize];
244        let mut function_code = &module_code[start_offset as usize..end_offset as usize];
245
246        log::debug!(
247            "Searching for functions from {:#010x} to {:#010x}",
248            start_address,
249            end_address
250        );
251
252        // Upper bound for function search
253        let last_function_address = search_options.last_function_address.unwrap_or(end_address);
254        let mut upper_bounds = BTreeSet::new();
255
256        // Used to limit how far to search for valid function starts, see `max_function_start_search_distance`
257        let mut prev_valid_address = start_address;
258        let mut address = start_address;
259
260        while !function_code.is_empty()
261            && address <= *upper_bounds.first().unwrap_or(&last_function_address)
262        {
263            let thumb = Function::is_thumb_function(address, function_code);
264
265            let parse_mode = if thumb { ParseMode::Thumb } else { ParseMode::Arm };
266            let parser =
267                Parser::new(parse_mode, address, Endian::Little, PARSE_FLAGS, function_code);
268
269            let (name, new) = if let Some((_, symbol)) = symbol_map.by_address(address)? {
270                (symbol.name.clone(), false)
271            } else {
272                (format!("{default_name_prefix}{address:08x}"), true)
273            };
274
275            let function_result = Function::function_parser_loop(parser, FunctionParseOptions {
276                name,
277                start_address: address,
278                base_address,
279                module_code,
280                known_end_address: None,
281                module_start_address,
282                module_end_address,
283                existing_functions: search_options.existing_functions,
284                check_defs_uses: search_options.check_defs_uses,
285                parse_options: Default::default(),
286            });
287            let function = match function_result {
288                Ok(function) => function,
289                Err(FunctionAnalysisError::IntoFunction {
290                    source: IntoFunctionError::ParseFunction { source },
291                }) => {
292                    match source {
293                        ParseFunctionError::IllegalIns {
294                            address: illegal_address, ins, ..
295                        } => {
296                            let search_limit = prev_valid_address
297                                .saturating_add(search_options.max_function_start_search_distance);
298                            let limit_reached = address >= search_limit;
299
300                            if !limit_reached {
301                                // It's possible that we've attempted to analyze pool constants as code, which can happen if the
302                                // function has a constant pool ahead of its code.
303                                let mut next_address = (address + 1).next_multiple_of(4);
304                                if let Some(function_addresses) =
305                                    search_options.function_addresses.as_ref()
306                                    && let Some(&next_function) =
307                                        function_addresses.range(address + 1..).next()
308                                {
309                                    next_address = next_function;
310                                }
311                                address = next_address;
312                                function_code = &module_code[(address - base_address) as usize..];
313                                continue;
314                            } else {
315                                if thumb {
316                                    log::debug!(
317                                        "Terminating function analysis due to illegal instruction at {:#010x}: {:04x}",
318                                        illegal_address,
319                                        ins.code()
320                                    );
321                                } else {
322                                    log::debug!(
323                                        "Terminating function analysis due to illegal instruction at {:#010x}: {:08x}",
324                                        illegal_address,
325                                        ins.code()
326                                    );
327                                }
328                                break;
329                            }
330                        }
331                        ParseFunctionError::NoEpilogue => {
332                            log::debug!(
333                                "Terminating function analysis due to no epilogue in function starting from {:#010x}",
334                                address
335                            );
336                            break;
337                        }
338                        ParseFunctionError::InvalidStart { address: start_address, ins } => {
339                            let search_limit = prev_valid_address
340                                .saturating_add(search_options.max_function_start_search_distance);
341                            let limit_reached = address >= search_limit;
342
343                            if !limit_reached {
344                                let ins_size = parse_mode.instruction_size(0);
345                                address += ins_size as u32;
346                                function_code = &function_code[ins_size..];
347                                continue;
348                            } else {
349                                log::debug!(
350                                    "Terminating function analysis due to invalid function start at {:#010x}: {}",
351                                    start_address,
352                                    ins
353                                );
354                                break;
355                            }
356                        }
357                    }
358                }
359                Err(e) => return Err(e),
360            };
361
362            // A function was found
363            if new {
364                symbol_map.add_function(&function);
365            }
366            function.add_local_symbols_to_map(symbol_map)?;
367
368            address = function.end_address.next_multiple_of(4); // align by 4 in case of Thumb function ending on 2-byte boundary
369            prev_valid_address = function.end_address;
370            function_code = &module_code[(address - base_address) as usize..];
371
372            // Invalidate upper bounds if they are inside the function
373            let invalid_upper_bounds: Vec<u32> =
374                upper_bounds.range(..=function.end_address).copied().collect();
375            for invalid_upper_bound in invalid_upper_bounds {
376                upper_bounds.remove(&invalid_upper_bound);
377                log::debug!(
378                    "Invalidating upper bound {:#010x} inside function {:#010x}",
379                    invalid_upper_bound,
380                    function.start_address
381                );
382            }
383
384            // Look for pointers to data in this module, to use as an upper bound for finding functions
385            if search_options.use_data_as_upper_bound {
386                for pool_constant in function.iter_pool_constants(module_code, base_address) {
387                    let pointer_value = pool_constant.value & !1;
388                    if upper_bounds.contains(&pointer_value) {
389                        continue;
390                    }
391                    if pointer_value < address {
392                        continue;
393                    }
394
395                    let offset = (pointer_value - base_address) as usize;
396                    if offset >= module_code.len() {
397                        continue;
398                    }
399
400                    let thumb = Function::is_thumb_function(pointer_value, &module_code[offset..]);
401                    let mut parser = Parser::new(
402                        if thumb { ParseMode::Thumb } else { ParseMode::Arm },
403                        pointer_value,
404                        Endian::Little,
405                        PARSE_FLAGS,
406                        &module_code[offset..],
407                    );
408                    let (address, ins, parsed_ins) = parser.next().unwrap();
409                    if is_valid_function_start(address, ins, &parsed_ins) {
410                        continue;
411                    }
412
413                    // The pool constant points to data, limit the upper bound
414                    upper_bounds.insert(pointer_value);
415                    log::debug!(
416                        "Upper bound found: address to data at {:#010x} from pool constant at {:#010x} from function {}",
417                        pool_constant.value,
418                        pool_constant.address,
419                        function.name
420                    );
421                }
422            }
423
424            functions.insert(function.first_instruction_address, function);
425        }
426        Ok(functions)
427    }
428
429    pub fn add_local_symbols_to_map(
430        &self,
431        symbol_map: &mut SymbolMap,
432    ) -> Result<(), SymbolMapError> {
433        for address in self.labels.iter() {
434            symbol_map.add_label(*address, self.thumb)?;
435        }
436        for address in self.pool_constants.iter() {
437            symbol_map.add_pool_constant(*address)?;
438        }
439        for jump_table in self.jump_tables() {
440            symbol_map.add_jump_table(jump_table)?;
441        }
442        for inline_table in self.inline_tables().values() {
443            symbol_map.add_skip_data(None, inline_table.address, (*inline_table).into())?;
444        }
445        Ok(())
446    }
447
448    pub fn find_secure_area_functions(
449        module_code: &[u8],
450        base_addr: u32,
451        symbol_map: &mut SymbolMap,
452    ) -> BTreeMap<u32, Function> {
453        let mut functions = BTreeMap::new();
454
455        let mut address = base_addr;
456        let mut state = SecureAreaState::default();
457        for ins_code in module_code.chunks_exact(2) {
458            let ins_code = u16::from_le_slice(ins_code);
459            let ins = thumb::Ins::new(ins_code as u32, &PARSE_FLAGS);
460            let parsed_ins = ins.parse(&PARSE_FLAGS);
461
462            state = state.handle(address, &parsed_ins);
463            if let Some(function) = state.get_function() {
464                let function = Function {
465                    name: function.name().to_string(),
466                    start_address: function.start(),
467                    end_address: function.end(),
468                    first_instruction_address: function.start(),
469                    thumb: true,
470                    labels: Labels::new(),
471                    pool_constants: PoolConstants::new(),
472                    jump_tables: JumpTables::new(),
473                    inline_tables: InlineTables::new(),
474                    function_calls: FunctionCalls::new(),
475                };
476                symbol_map.add_function(&function);
477                functions.insert(function.first_instruction_address, function);
478            }
479
480            address += 2;
481        }
482
483        functions
484    }
485
486    pub fn parser<'a>(&'a self, module_code: &'a [u8], base_address: u32) -> Parser<'a> {
487        Parser::new(
488            if self.thumb { ParseMode::Thumb } else { ParseMode::Arm },
489            self.start_address,
490            Endian::Little,
491            PARSE_FLAGS,
492            self.code(module_code, base_address),
493        )
494    }
495
496    pub fn code<'a>(&self, module_code: &'a [u8], base_address: u32) -> &'a [u8] {
497        let start = (self.start_address - base_address) as usize;
498        let end = (self.end_address - base_address) as usize;
499        &module_code[start..end]
500    }
501
502    pub fn name(&self) -> &str {
503        &self.name
504    }
505
506    pub fn start_address(&self) -> u32 {
507        self.start_address
508    }
509
510    pub fn end_address(&self) -> u32 {
511        self.end_address
512    }
513
514    pub fn first_instruction_address(&self) -> u32 {
515        self.first_instruction_address
516    }
517
518    pub fn is_thumb(&self) -> bool {
519        self.thumb
520    }
521
522    pub fn labels(&self) -> impl Iterator<Item = &u32> {
523        self.labels.iter()
524    }
525
526    pub fn jump_tables(&self) -> impl Iterator<Item = &JumpTable> {
527        self.jump_tables.values()
528    }
529
530    pub fn inline_tables(&self) -> &InlineTables {
531        &self.inline_tables
532    }
533
534    pub fn get_inline_table_at(&self, address: u32) -> Option<&InlineTable> {
535        Self::inline_table_at(&self.inline_tables, address)
536    }
537
538    fn inline_table_at(inline_tables: &InlineTables, address: u32) -> Option<&InlineTable> {
539        inline_tables
540            .values()
541            .find(|table| address >= table.address && address < table.address + table.size)
542    }
543
544    pub fn pool_constants(&self) -> &PoolConstants {
545        &self.pool_constants
546    }
547
548    pub fn iter_pool_constants<'a>(
549        &'a self,
550        module_code: &'a [u8],
551        base_address: u32,
552    ) -> impl Iterator<Item = PoolConstant> + 'a {
553        self.pool_constants.iter().map(move |&address| {
554            let start = (address - base_address) as usize;
555            let bytes = &module_code[start..];
556            PoolConstant { address, value: u32::from_le_slice(bytes) }
557        })
558    }
559
560    pub fn function_calls(&self) -> &FunctionCalls {
561        &self.function_calls
562    }
563}
564
565#[derive(Default)]
566pub struct FunctionParseOptions<'a> {
567    pub name: String,
568    pub start_address: u32,
569    pub base_address: u32,
570    pub module_code: &'a [u8],
571    pub known_end_address: Option<u32>,
572    pub module_start_address: u32,
573    pub module_end_address: u32,
574    pub existing_functions: Option<&'a BTreeMap<u32, Function>>,
575
576    /// Whether to check that all registers used in the instruction are defined
577    pub check_defs_uses: bool,
578
579    pub parse_options: ParseFunctionOptions,
580}
581
582pub struct FindFunctionsOptions<'a> {
583    pub default_name_prefix: &'a str,
584    pub base_address: u32,
585    pub module_code: &'a [u8],
586    pub symbol_map: &'a mut SymbolMap,
587    pub module_start_address: u32,
588    pub module_end_address: u32,
589
590    pub search_options: FunctionSearchOptions<'a>,
591}
592
593struct ParseFunctionContext<'a> {
594    name: String,
595    start_address: u32,
596    thumb: bool,
597    end_address: Option<u32>,
598    known_end_address: Option<u32>,
599    labels: Labels,
600    pool_constants: PoolConstants,
601    jump_tables: JumpTables,
602    inline_tables: InlineTables,
603    function_calls: FunctionCalls,
604
605    module_start_address: u32,
606    module_end_address: u32,
607    existing_functions: Option<&'a BTreeMap<u32, Function>>,
608
609    /// Address of last conditional instruction, so we can detect the final return instruction
610    last_conditional_destination: Option<u32>,
611    /// Address of last pool constant, to get the function's true end address
612    last_pool_address: Option<u32>,
613    /// State machine for detecting jump tables and adding them as symbols
614    jump_table_state: JumpTableState,
615    /// State machine for detecting branches (B, not BL) to other functions
616    function_branch_state: FunctionBranchState,
617    /// State machine for detecting inline data tables within the function
618    inline_table_state: InlineTableState,
619    /// State machine for detecting illegal code sequences
620    illegal_code_state: IllegalCodeState,
621
622    /// Whether to check that all registers used in the instruction are defined
623    check_defs_uses: bool,
624    defined_registers: BTreeSet<Register>,
625
626    prev_ins: Option<Ins>,
627    prev_parsed_ins: Option<ParsedIns>,
628    prev_address: Option<u32>,
629}
630
631#[derive(Debug, Snafu)]
632pub enum IntoFunctionError {
633    #[snafu(display("Cannot turn parse context into function before parsing is done"))]
634    NotDone { backtrace: Backtrace },
635    #[snafu(transparent)]
636    ParseFunction { source: ParseFunctionError },
637}
638
639impl<'a> ParseFunctionContext<'a> {
640    pub fn new(thumb: bool, options: FunctionParseOptions<'a>) -> Self {
641        let FunctionParseOptions {
642            name,
643            start_address,
644            known_end_address,
645            module_start_address,
646            module_end_address,
647            existing_functions,
648            check_defs_uses,
649            ..
650        } = options;
651
652        let mut defined_registers = BTreeSet::new();
653        // Could be arguments
654        defined_registers.insert(Register::R0);
655        defined_registers.insert(Register::R1);
656        defined_registers.insert(Register::R2);
657        defined_registers.insert(Register::R3);
658        // Always defined
659        defined_registers.insert(Register::Sp);
660        defined_registers.insert(Register::Lr);
661        defined_registers.insert(Register::Pc);
662        // Could be used as a scratch register
663        defined_registers.insert(Register::R12);
664
665        Self {
666            name,
667            start_address,
668            thumb,
669            end_address: None,
670            known_end_address,
671            labels: Labels::new(),
672            pool_constants: PoolConstants::new(),
673            jump_tables: JumpTables::new(),
674            inline_tables: InlineTables::new(),
675            function_calls: FunctionCalls::new(),
676
677            module_start_address,
678            module_end_address,
679            existing_functions,
680
681            last_conditional_destination: None,
682            last_pool_address: None,
683            jump_table_state: if thumb {
684                JumpTableState::Thumb(Default::default())
685            } else {
686                JumpTableState::Arm(Default::default())
687            },
688            function_branch_state: Default::default(),
689            inline_table_state: Default::default(),
690            illegal_code_state: Default::default(),
691
692            check_defs_uses,
693            defined_registers,
694
695            prev_ins: None,
696            prev_parsed_ins: None,
697            prev_address: None,
698        }
699    }
700
701    fn handle_ins_inner(
702        &mut self,
703        parser: &mut Parser,
704        address: u32,
705        ins: Ins,
706        parsed_ins: &ParsedIns,
707    ) -> ParseFunctionState {
708        if self.pool_constants.contains(&address) {
709            parser.seek_forward(address + 4);
710            return ParseFunctionState::Continue;
711        }
712        if let Some(inline_table) = Function::inline_table_at(&self.inline_tables, address) {
713            parser.seek_forward(inline_table.address + inline_table.size);
714            return ParseFunctionState::Continue;
715        }
716
717        self.jump_table_state =
718            self.jump_table_state.handle(address, ins, parsed_ins, &mut self.jump_tables);
719        self.last_conditional_destination =
720            self.last_conditional_destination.max(self.jump_table_state.table_end_address());
721        if let Some(label) = self.jump_table_state.get_label(address, ins) {
722            self.labels.insert(label);
723            self.last_conditional_destination = self.last_conditional_destination.max(Some(label));
724        }
725
726        if self.jump_table_state.is_numerical_jump_offset() {
727            // Not an instruction, continue
728            return ParseFunctionState::Continue;
729        }
730
731        let ins_size = if let Ins::Thumb(thumb_ins) = ins {
732            if thumb_ins.op != thumb::Opcode::Bl && thumb_ins.op != thumb::Opcode::BlxI {
733                // Typical Thumb instruction
734                2
735            } else if matches!(parsed_ins.args[0], Argument::BranchDest(_)) {
736                // Combined BL/BLX instruction
737                4
738            } else {
739                // Not combined
740                return ParseFunctionState::IllegalIns { address, ins };
741            }
742        } else {
743            // ARM instruction
744            4
745        };
746
747        self.illegal_code_state = self.illegal_code_state.handle(ins, parsed_ins);
748        if self.illegal_code_state.is_illegal() {
749            return ParseFunctionState::IllegalIns { address, ins };
750        }
751
752        let in_conditional_block = Some(address) < self.last_conditional_destination;
753        let is_return = self.is_return(
754            ins,
755            parsed_ins,
756            address,
757            self.start_address,
758            self.module_start_address,
759            self.module_end_address,
760        );
761        if !in_conditional_block && is_return {
762            let end_address = address + ins_size;
763            if let Some(destination) = Function::is_branch(ins, parsed_ins, address) {
764                let outside_function =
765                    destination < self.start_address || destination >= end_address;
766                if outside_function {
767                    // Tail call
768                    self.function_calls.insert(address, CalledFunction {
769                        ins,
770                        address: destination,
771                        thumb: self.thumb,
772                    });
773                }
774            }
775
776            // We're not inside a conditional code block, so this is the final return instruction
777            self.end_address = Some(address + ins_size);
778            return ParseFunctionState::Done;
779        }
780
781        if address > self.start_address
782            && Function::is_entry_instruction(ins, parsed_ins)
783            && let Some(prev_ins) = self.prev_ins
784            && let Some(prev_parsed_ins) = self.prev_parsed_ins.as_ref()
785            && let Some(prev_address) = self.prev_address
786            && Function::is_branch(prev_ins, prev_parsed_ins, prev_address).is_some()
787        {
788            let is_conditional = in_conditional_block || prev_ins.is_conditional();
789            if is_conditional {
790                // Tail call
791                self.end_address = Some(address);
792                return ParseFunctionState::Done;
793            }
794        }
795
796        self.function_branch_state = self.function_branch_state.handle(ins, parsed_ins);
797        if let Some(destination) = Function::is_branch(ins, parsed_ins, address) {
798            let in_current_module =
799                destination >= self.module_start_address && destination < self.module_end_address;
800            if !in_current_module {
801                // Tail call
802                self.function_calls.insert(address, CalledFunction {
803                    ins,
804                    address: destination,
805                    thumb: self.thumb,
806                });
807            } else if self.function_branch_state.is_function_branch()
808                || self
809                    .existing_functions
810                    .map(|functions| functions.contains_key(&destination))
811                    .unwrap_or(false)
812            {
813                if !ins.is_conditional() && !in_conditional_block {
814                    // This is an unconditional backwards function branch, which means this function has ended
815                    self.end_address = Some(address + ins_size);
816                    return ParseFunctionState::Done;
817                } else {
818                    // TODO: Always run this (move it outside of else block). SectionExt::relocatable_code must take condition
819                    // code into account so the game matches after linking
820                    self.function_calls.insert(address, CalledFunction {
821                        ins,
822                        address: destination,
823                        thumb: self.thumb,
824                    });
825                }
826            } else {
827                // Normal branch instruction, insert a label
828                if let Some(state) = self.handle_label(destination, address, parser, ins_size) {
829                    return state;
830                }
831            }
832        }
833
834        if let Some(pool_address) = Function::is_pool_load(ins, parsed_ins, address, self.thumb) {
835            self.pool_constants.insert(pool_address);
836            self.last_pool_address = self.last_pool_address.max(Some(pool_address));
837        }
838
839        self.inline_table_state = self.inline_table_state.handle(self.thumb, address, parsed_ins);
840        if let Some(table) = self.inline_table_state.get_table() {
841            log::debug!("Inline table found at {:#x}, size {:#x}", table.address, table.size);
842            self.inline_tables.insert(table.address, table);
843        }
844
845        if let Some(called_function) =
846            Function::is_function_call(ins, parsed_ins, address, self.thumb)
847        {
848            self.function_calls.insert(address, called_function);
849        }
850
851        if self.check_defs_uses && !Self::is_nop(ins, parsed_ins) {
852            if Self::is_push(ins) {
853                // Add all caller-saved registers to the defined set
854                ins.register_list().iter().for_each(|reg| {
855                    self.defined_registers.insert(reg);
856                });
857            }
858
859            // Verify that all registers used in the instruction are defined
860            let defs_uses = match ins {
861                Ins::Arm(ins) => {
862                    Some((ins.defs(&Default::default()), ins.uses(&Default::default())))
863                }
864                Ins::Thumb(ins) => {
865                    Some((ins.defs(&Default::default()), ins.uses(&Default::default())))
866                }
867                Ins::Data => None,
868            };
869            if let Some((defs, uses)) = defs_uses {
870                for usage in uses {
871                    let legal = match usage {
872                        Argument::Reg(reg) => {
873                            if let Ins::Arm(ins) = ins
874                                && ins.op == arm::Opcode::Str
875                                && ins.field_rn_deref().reg == Register::Sp
876                            {
877                                // There are instance of `str Rd, [sp, #imm]` where Rd is not defined.
878                                // Potential UB bug in mwccarm.
879                                self.defined_registers.insert(reg.reg);
880                                continue;
881                            }
882
883                            self.defined_registers.contains(&reg.reg)
884                        }
885                        Argument::RegList(reg_list) => {
886                            reg_list.iter().all(|reg| self.defined_registers.contains(&reg))
887                        }
888                        Argument::ShiftReg(shift_reg) => {
889                            self.defined_registers.contains(&shift_reg.reg)
890                        }
891                        Argument::OffsetReg(offset_reg) => {
892                            self.defined_registers.contains(&offset_reg.reg)
893                        }
894                        _ => continue,
895                    };
896                    if !legal {
897                        return ParseFunctionState::IllegalIns { address, ins };
898                    }
899                }
900                if !is_return {
901                    for def in defs {
902                        match def {
903                            Argument::Reg(reg) => {
904                                self.defined_registers.insert(reg.reg);
905                            }
906                            Argument::RegList(reg_list) => {
907                                for reg in reg_list.iter() {
908                                    self.defined_registers.insert(reg);
909                                }
910                            }
911                            Argument::ShiftReg(shift_reg) => {
912                                self.defined_registers.insert(shift_reg.reg);
913                            }
914                            Argument::OffsetReg(offset_reg) => {
915                                self.defined_registers.insert(offset_reg.reg);
916                            }
917                            _ => continue,
918                        };
919                    }
920                }
921            }
922        }
923
924        ParseFunctionState::Continue
925    }
926
927    pub fn handle_ins(
928        &mut self,
929        parser: &mut Parser,
930        address: u32,
931        ins: Ins,
932        parsed_ins: ParsedIns,
933    ) -> ParseFunctionState {
934        let state = self.handle_ins_inner(parser, address, ins, &parsed_ins);
935        self.prev_ins = Some(ins);
936        self.prev_parsed_ins = Some(parsed_ins);
937        self.prev_address = Some(address);
938        state
939    }
940
941    fn handle_label(
942        &mut self,
943        destination: u32,
944        address: u32,
945        parser: &mut Parser,
946        ins_size: u32,
947    ) -> Option<ParseFunctionState> {
948        self.labels.insert(destination);
949        self.last_conditional_destination =
950            self.last_conditional_destination.max(Some(destination));
951
952        let next_address = address + ins_size;
953        if self.pool_constants.contains(&next_address) {
954            let branch_backwards = destination <= address;
955
956            // Load instructions in ARM mode can have an offset of up to ±4kB. Therefore, some functions must
957            // emit pool constants in the middle so they can all be accessed by PC-relative loads. There will
958            // also be branch instruction right before, so that the pool constants don't get executed.
959
960            // Sometimes, the pre-pool branch is conveniently placed at an actual branch in the code, and
961            // leads even further than the end of the pool constants. In that case we should already have found
962            // a label at a lower address.
963            if let Some(after_pools) = self.labels.range(address + 1..).next().copied() {
964                if after_pools > address + 0x1000 {
965                    log::warn!(
966                        "Massive gap from constant pool at {:#x} to next label at {:#x}",
967                        next_address,
968                        after_pools
969                    );
970                }
971                parser.seek_forward(after_pools);
972            } else if !branch_backwards {
973                // Backwards branch with no further branch labels. This type of function contains some kind of infinite loop,
974                // hence the lack of return instruction as the final instruction.
975                self.end_address = Some(next_address);
976                return Some(ParseFunctionState::Done);
977            } else {
978                let after_pools = (next_address..)
979                    .step_by(4)
980                    .find(|addr| !self.pool_constants.contains(addr))
981                    .unwrap();
982                log::warn!(
983                    "No label past constant pool at {:#x}, jumping to first address not occupied by a pool constant ({:#x})",
984                    next_address,
985                    after_pools
986                );
987                parser.seek_forward(after_pools);
988            }
989        }
990
991        None
992    }
993
994    fn into_function(self, state: ParseFunctionState) -> Result<Function, IntoFunctionError> {
995        match state {
996            ParseFunctionState::Continue => {
997                return NotDoneSnafu.fail();
998            }
999            ParseFunctionState::IllegalIns { address, ins } => {
1000                return IllegalInsSnafu { address, ins }.fail()?;
1001            }
1002            ParseFunctionState::Done => {}
1003        };
1004        let Some(end_address) = self.end_address else {
1005            return NoEpilogueSnafu.fail()?;
1006        };
1007
1008        let end_address = self
1009            .known_end_address
1010            .unwrap_or(end_address.max(self.last_pool_address.map(|a| a + 4).unwrap_or(0)));
1011        if end_address > self.module_end_address {
1012            return NoEpilogueSnafu.fail()?;
1013        }
1014
1015        Ok(Function {
1016            name: self.name,
1017            start_address: self.start_address,
1018            end_address,
1019            first_instruction_address: self.start_address,
1020            thumb: self.thumb,
1021            labels: self.labels,
1022            pool_constants: self.pool_constants,
1023            jump_tables: self.jump_tables,
1024            inline_tables: self.inline_tables,
1025            function_calls: self.function_calls,
1026        })
1027    }
1028
1029    fn is_return(
1030        &self,
1031        ins: Ins,
1032        parsed_ins: &ParsedIns,
1033        address: u32,
1034        function_start: u32,
1035        module_start_address: u32,
1036        module_end_address: u32,
1037    ) -> bool {
1038        if ins.is_conditional() {
1039            return false;
1040        }
1041
1042        let args = &parsed_ins.args;
1043        match (parsed_ins.mnemonic, args[0], args[1]) {
1044            // bx *
1045            ("bx", _, _) => true,
1046            // mov pc, *
1047            ("mov", Argument::Reg(Reg { reg: Register::Pc, .. }), _) => true,
1048            // ldmia *, {..., pc}
1049            ("ldmia", _, Argument::RegList(reg_list)) if reg_list.contains(Register::Pc) => true,
1050            // pop {..., pc}
1051            ("pop", Argument::RegList(reg_list), _) if reg_list.contains(Register::Pc) => true,
1052            // backwards branch
1053            ("b", Argument::BranchDest(offset), _) if offset < 0 => {
1054                // Branch must be within current function (infinite loop) or outside current module (tail call)
1055                Function::is_branch(ins, parsed_ins, address)
1056                    .map(|destination| {
1057                        destination >= function_start
1058                            || destination < module_start_address
1059                            || destination >= module_end_address
1060                    })
1061                    .unwrap_or(false)
1062            }
1063            // subs pc, lr, *
1064            (
1065                "subs",
1066                Argument::Reg(Reg { reg: Register::Pc, .. }),
1067                Argument::Reg(Reg { reg: Register::Lr, .. }),
1068            ) => true,
1069            // ldr pc, *
1070            ("ldr", Argument::Reg(Reg { reg: Register::Pc, .. }), _) => true,
1071            _ => false,
1072        }
1073    }
1074
1075    fn is_nop(ins: Ins, parsed_ins: &ParsedIns) -> bool {
1076        match (ins.mnemonic(), parsed_ins.args[0], parsed_ins.args[1], parsed_ins.args[2]) {
1077            ("nop", _, _, _) => true,
1078            (
1079                "mov",
1080                Argument::Reg(Reg { reg: dest, .. }),
1081                Argument::Reg(Reg { reg: src, .. }),
1082                Argument::None,
1083            ) => dest == src,
1084            _ => false,
1085        }
1086    }
1087
1088    fn is_push(ins: Ins) -> bool {
1089        match ins {
1090            Ins::Arm(arm_ins) => {
1091                if arm_ins.op == arm::Opcode::StmW && arm_ins.field_rn_wb().reg == Register::Sp {
1092                    true
1093                } else {
1094                    matches!(arm_ins.op, arm::Opcode::PushM | arm::Opcode::PushR)
1095                }
1096            }
1097            Ins::Thumb(thumb_ins) => thumb_ins.op == thumb::Opcode::Push,
1098            _ => false,
1099        }
1100    }
1101}
1102
1103#[derive(Default)]
1104pub struct ParseFunctionOptions {
1105    /// Whether the function is in Thumb or ARM mode, or None if it should be detected automatically.
1106    pub thumb: Option<bool>,
1107}
1108
1109enum ParseFunctionState {
1110    Continue,
1111    IllegalIns { address: u32, ins: Ins },
1112    Done,
1113}
1114
1115impl ParseFunctionState {
1116    pub fn ended(&self) -> bool {
1117        match self {
1118            Self::Continue => false,
1119            Self::IllegalIns { .. } | Self::Done => true,
1120        }
1121    }
1122}
1123
1124#[derive(Debug, Snafu)]
1125pub enum ParseFunctionError {
1126    #[snafu(display("Illegal instruction at {address:#010x}: {ins:?}"))]
1127    IllegalIns { address: u32, ins: Ins },
1128    #[snafu(display("No epilogue found"))]
1129    NoEpilogue,
1130    #[snafu(display("Illegal function start at {address:#010x}: {ins}"))]
1131    InvalidStart { address: u32, ins: DisplayIns },
1132}
1133
1134#[derive(Debug)]
1135pub struct DisplayIns(Ins);
1136
1137impl From<Ins> for DisplayIns {
1138    fn from(value: Ins) -> Self {
1139        Self(value)
1140    }
1141}
1142
1143impl Display for DisplayIns {
1144    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
1145        let parsed_ins = match self.0 {
1146            Ins::Arm(ins) => ins.parse(&PARSE_FLAGS),
1147            Ins::Thumb(ins) => ins.parse(&PARSE_FLAGS),
1148            Ins::Data => return write!(f, "<data>"),
1149        };
1150        write!(f, "{}", parsed_ins.display(Default::default()))
1151    }
1152}
1153
1154#[derive(Default)]
1155pub struct FunctionSearchOptions<'a> {
1156    /// Address to start searching from. Defaults to the base address.
1157    pub start_address: Option<u32>,
1158    /// Last address that a function can start from. Defaults to [`Self::end_address`].
1159    pub last_function_address: Option<u32>,
1160    /// Address to end the search. Defaults to the base address plus code size.
1161    pub end_address: Option<u32>,
1162    /// If zero, end the search when an illegal starting instruction is found. Otherwise, continue searching for a valid
1163    /// function start for up to this many bytes. Set to [`u32::MAX`] to search until the end of the module.
1164    pub max_function_start_search_distance: u32,
1165    /// If true, pointers to data will be used to limit the upper bound address.
1166    pub use_data_as_upper_bound: bool,
1167    /// Guarantees that all these addresses will be analyzed, even if the function analysis would terminate before they are
1168    /// reached. Used for .init functions.
1169    /// Note: This will override `keep_searching_for_valid_function_start`, they are not intended to be used together.
1170    pub function_addresses: Option<BTreeSet<u32>>,
1171    /// If a branch instruction branches into one of these functions, it will be treated as a function branch instead of
1172    /// inserting a label at the branch destination.
1173    /// If the function branch is unconditional, it will also be treated as a tail call and terminate the analysis of the
1174    /// current function.
1175    pub existing_functions: Option<&'a BTreeMap<u32, Function>>,
1176    /// Whether to treat instructions using undefined registers as illegal.
1177    pub check_defs_uses: bool,
1178}
1179
1180#[derive(Clone, Copy, Debug)]
1181pub struct CalledFunction {
1182    pub ins: Ins,
1183    pub address: u32,
1184    pub thumb: bool,
1185}
1186
1187pub struct PoolConstant {
1188    pub address: u32,
1189    pub value: u32,
1190}