From b8f401b0bacfae9ea65e35d4b89f82c9da43f853 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1vid=20Balatoni?= <40299962+dbalatoni13@users.noreply.github.com> Date: Fri, 10 Jul 2026 07:45:53 +0200 Subject: [PATCH] Make the .mdebug line parser support EE-GCC (PS2) (#362) --- objdiff-core/src/obj/mdebug.rs | 98 +++++++++++++++--- objdiff-core/tests/arch_mips.rs | 25 +++++ .../tests/data/mips/ee_gcc_lines_example.o | Bin 0 -> 3348 bytes 3 files changed, 107 insertions(+), 16 deletions(-) create mode 100644 objdiff-core/tests/data/mips/ee_gcc_lines_example.o diff --git a/objdiff-core/src/obj/mdebug.rs b/objdiff-core/src/obj/mdebug.rs index 17b93c9..018d170 100644 --- a/objdiff-core/src/obj/mdebug.rs +++ b/objdiff-core/src/obj/mdebug.rs @@ -10,6 +10,7 @@ const FDR_SIZE: usize = 0x48; const PDR_SIZE: usize = 0x34; const SYMR_SIZE: usize = 0x0c; +const ST_LABEL: u8 = 5; const ST_PROC: u8 = 6; const ST_STATICPROC: u8 = 14; const ST_END: u8 = 8; @@ -40,6 +41,8 @@ pub(super) fn parse_line_info_mdebug( )?; let symbols = parse_symbols(symbols_data, endianness)?; + let strings_data = slice_at(data, header.cb_ss_offset, header.iss_max, section_file_offset)?; + let fdr_data = slice_at( data, header.cb_fd_offset, @@ -63,11 +66,13 @@ pub(super) fn parse_line_info_mdebug( continue; } + assign_label_lines(sections, &symbols[sym_base..sym_end], strings_data, &fdr); + let Some(line_file_offset) = header.cb_line_offset.checked_add(fdr.cb_line_offset) else { continue; }; let Some(line_file_base) = - resolve_offset(line_file_offset, data.len(), section_file_offset) + resolve_offset(line_file_offset, fdr.cb_line as usize, data.len(), section_file_offset) else { continue; }; @@ -168,6 +173,44 @@ fn assign_lines(sections: &mut [Section], base_address: u64, lines: &[i32]) { } } +fn assign_label_lines( + sections: &mut [Section], + symbols: &[SymbolEntry], + strings: &[u8], + fdr: &FileDescriptor, +) { + for sym in symbols { + if sym.st != ST_LABEL || sym.index == 0 { + continue; + } + let Some(name) = symbol_name(strings, fdr.iss_base, sym.iss) else { + continue; + }; + if !name.starts_with("$LM") { + continue; + } + let address = fdr.adr as u64 + sym.value as u64; + if let Some(section) = find_code_section(sections, address) { + section.line_info.insert(address, sym.index); + } + } +} + +fn symbol_name(strings: &[u8], file_string_base: u32, string_offset: u32) -> Option<&str> { + let offset = + string_offset.try_into().ok().filter(|&offset: &usize| offset < strings.len()).or_else( + || { + file_string_base + .checked_add(string_offset)? + .try_into() + .ok() + .filter(|&offset: &usize| offset < strings.len()) + }, + )?; + let end = offset + strings[offset..].iter().position(|&b| b == 0)?; + core::str::from_utf8(&strings[offset..end]).ok() +} + fn find_code_section(sections: &mut [Section], address: u64) -> Option<&mut Section> { sections.iter_mut().find(|section| { section.kind == SectionKind::Code @@ -209,12 +252,12 @@ fn slice_at( let size = size as usize; if size == 0 { ensure!( - resolve_offset(offset, data.len(), section_file_offset).is_some(), + resolve_offset(offset, 0, data.len(), section_file_offset).is_some(), "offset outside of .mdebug section" ); return Ok(&data[0..0]); } - let Some(offset) = resolve_offset(offset, data.len(), section_file_offset) else { + let Some(offset) = resolve_offset(offset, size, data.len(), section_file_offset) else { bail!("offset outside of .mdebug section"); }; let end = offset.checked_add(size).context("range overflow")?; @@ -224,17 +267,18 @@ fn slice_at( fn resolve_offset( offset: u32, + size: usize, data_len: usize, section_file_offset: Option, ) -> Option { let offset = offset as usize; - if offset <= data_len { - Some(offset) - } else if let Some(file_offset) = section_file_offset { - offset.checked_sub(file_offset).filter(|rel| *rel <= data_len) - } else { - None + if let Some(file_offset) = section_file_offset + && let Some(rel) = offset.checked_sub(file_offset) + && rel.checked_add(size).is_some_and(|end| end <= data_len) + { + return Some(rel); } + offset.checked_add(size).filter(|&end| end <= data_len).map(|_| offset) } #[derive(Clone, Copy)] @@ -242,8 +286,10 @@ struct Header { cb_line_offset: u32, cb_pd_offset: u32, cb_sym_offset: u32, + cb_ss_offset: u32, cb_fd_offset: u32, isym_max: u32, + iss_max: u32, ifd_max: u32, } @@ -267,8 +313,8 @@ impl Header { let _cb_opt_offset = read_u32(data, &mut cursor, endianness)?; let _iaux_max = read_u32(data, &mut cursor, endianness)?; let _cb_aux_offset = read_u32(data, &mut cursor, endianness)?; - let _iss_max = read_u32(data, &mut cursor, endianness)?; - let _cb_ss_offset = read_u32(data, &mut cursor, endianness)?; + let iss_max = read_u32(data, &mut cursor, endianness)?; + let cb_ss_offset = read_u32(data, &mut cursor, endianness)?; let _iss_ext_max = read_u32(data, &mut cursor, endianness)?; let _cb_ss_ext_offset = read_u32(data, &mut cursor, endianness)?; let ifd_max = read_u32(data, &mut cursor, endianness)?; @@ -278,13 +324,23 @@ impl Header { let _iext_max = read_u32(data, &mut cursor, endianness)?; let _cb_ext_offset = read_u32(data, &mut cursor, endianness)?; - Ok(Header { cb_line_offset, cb_pd_offset, cb_sym_offset, cb_fd_offset, isym_max, ifd_max }) + Ok(Header { + cb_line_offset, + cb_pd_offset, + cb_sym_offset, + cb_ss_offset, + cb_fd_offset, + isym_max, + iss_max, + ifd_max, + }) } } #[derive(Clone, Copy)] struct FileDescriptor { adr: u32, + iss_base: u32, isym_base: u32, csym: u32, ipd_first: u16, @@ -299,7 +355,7 @@ impl FileDescriptor { let mut cursor = 0; let adr = read_u32(data, &mut cursor, endianness)?; let _rss = read_u32(data, &mut cursor, endianness)?; - let _iss_base = read_u32(data, &mut cursor, endianness)?; + let iss_base = read_u32(data, &mut cursor, endianness)?; let _cb_ss = read_u32(data, &mut cursor, endianness)?; let isym_base = read_u32(data, &mut cursor, endianness)?; let csym = read_u32(data, &mut cursor, endianness)?; @@ -317,7 +373,16 @@ impl FileDescriptor { let cb_line_offset = read_u32(data, &mut cursor, endianness)?; let cb_line = read_u32(data, &mut cursor, endianness)?; - Ok(FileDescriptor { adr, isym_base, csym, ipd_first, cpd, cb_line_offset, cb_line }) + Ok(FileDescriptor { + adr, + iss_base, + isym_base, + csym, + ipd_first, + cpd, + cb_line_offset, + cb_line, + }) } } @@ -354,6 +419,7 @@ impl ProcDescriptor { #[derive(Clone, Copy)] struct SymbolEntry { + iss: u32, value: u32, st: u8, index: u32, @@ -364,14 +430,14 @@ fn parse_symbols(data: &[u8], endianness: Endianness) -> Result let mut symbols = Vec::with_capacity(data.len() / SYMR_SIZE); let mut cursor = 0; while cursor + SYMR_SIZE <= data.len() { - let _iss = read_u32(data, &mut cursor, endianness)?; + let iss = read_u32(data, &mut cursor, endianness)?; let value = read_u32(data, &mut cursor, endianness)?; let bits = read_u32(data, &mut cursor, endianness)?; let (st, index) = match endianness { Endianness::Big => (((bits >> 26) & 0x3f) as u8, bits & 0x000f_ffff), Endianness::Little => (((bits & 0x3f) as u8), (bits >> 12) & 0x000f_ffff), }; - symbols.push(SymbolEntry { value, st, index }); + symbols.push(SymbolEntry { iss, value, st, index }); } Ok(symbols) } diff --git a/objdiff-core/tests/arch_mips.rs b/objdiff-core/tests/arch_mips.rs index 81e701f..79ae008 100644 --- a/objdiff-core/tests/arch_mips.rs +++ b/objdiff-core/tests/arch_mips.rs @@ -102,3 +102,28 @@ fn mwcc_dwarf1_line_numbers_multiple_functions() { ); } } + +#[test] +#[cfg(feature = "mips")] +fn ee_gcc_mdebug_line_numbers() { + let diff_config = diff::DiffObjConfig::default(); + let obj = obj::read::parse( + include_object!("data/mips/ee_gcc_lines_example.o"), + &diff_config, + diff::DiffSide::Base, + ) + .unwrap(); + + let text_section = obj.sections.iter().find(|s| s.name == ".text").unwrap(); + assert_eq!(text_section.line_info.get(&0), Some(&1)); + assert_eq!(text_section.line_info.get(&12), Some(&2)); + assert_eq!(text_section.line_info.get(&20), Some(&4)); + assert_eq!(text_section.line_info.get(&40), Some(&5)); + assert_eq!(text_section.line_info.get(&64), Some(&7)); + assert_eq!(text_section.line_info.get(&80), Some(&8)); + assert_eq!(text_section.line_info.get(&88), Some(&10)); + assert_eq!(text_section.line_info.get(&96), Some(&11)); + assert_eq!(text_section.line_info.get(&104), Some(&12)); + assert_eq!(text_section.line_info.get(&144), Some(&13)); + assert_eq!(text_section.line_info.len(), 10); +} diff --git a/objdiff-core/tests/data/mips/ee_gcc_lines_example.o b/objdiff-core/tests/data/mips/ee_gcc_lines_example.o new file mode 100644 index 0000000000000000000000000000000000000000..62519654360c2ff70fe3783d25c748b9c1697e79 GIT binary patch literal 3348 zcmb<-^>JflWMqH=Mg|TB1doAX4i^IhgTo{R6R-je1}+A6Fzdnpz3KuC`~K^GSisD} zz@);!aA-XP!=Zjw24@vrfdr5l0w9IDFAgvZFzmapz_3@HgW&-)NDPE8{NJmtz_9;6 z%nT;D84Nric?JeFnxn}jfuq4~fdiur14E;W69FLw3nI1xs$FfcHPfWnM{fkB#qf#LuE|NlWD_yI!06)G?=Fvu~;gXN)W7#tWF z7&H)i4xsA+*@>fk{U|5XA2I;k7U|{grz{s!yYL2UeQNJS#1H%JwoLls-hnmU2VAH=9s+WPm zt{)V9AiYkGM*X{47{KxGT7c@d15j}{0|tguEDQ_*AU8BHFq~&$V2A**7cekfVPRlM z0I@ePFx+HeV8{Tm4=^y?XJKHN0AgQYV0ene2AK&`v@#i>QbG1-Yp znZ?B?&8-V)Va)}Yhize0v)(k26rAaxdRwahu)N26}0tX3P9GuR; z;>r2B1v#k|5SJU8+7??Br6%TB8R{757#mq<<|d|F8R{4tfjHI-FvTz%L6K=@4Ym>{ z1-Ake;O5pScA&b!!nVlJ0-~TSKQqOu#L&{#(2^msIK|44AtN;>C*R7D0kbr5$xklL zP0cGQj!DlfiBHbYD=ErMDlN&(&nu3}ODoPT*Jmh6EiTbZW>E3*HDpN3&j&G#K!h=f zFaZ&!AaN@ zB%~b!VIh-{N*byJSpt*&0o;CJ;D^aT`Jna-NXTsiq_kH7HF6jj7~DXO0lnhN+>*p3 z2EF2vA_$!UW0j;isbdc%9VNg2@ z+hB!yc}N5#EjfwV8w%7#UQd dX%5sL0r3guDYOg&YCo|sLfTFs8e|TL1^}krT4VqK literal 0 HcmV?d00001