1 use super::address_transform::AddressTransform; 2 use super::{Reader, TransformError}; 3 use anyhow::{Error, bail}; 4 use gimli::{DebugLineOffset, LineEncoding, Unit, write}; 5 use wasmtime_environ::DefinedFuncIndex; 6 7 #[derive(Debug)] 8 enum SavedLineProgramRow { 9 Normal { 10 address: u64, 11 op_index: u64, 12 file_index: u64, 13 line: u64, 14 column: u64, 15 discriminator: u64, 16 is_stmt: bool, 17 basic_block: bool, 18 prologue_end: bool, 19 epilogue_begin: bool, 20 isa: u64, 21 }, 22 EndOfSequence, 23 } 24 25 #[derive(Debug)] 26 struct FuncRows { 27 index: DefinedFuncIndex, 28 sorted_rows: Vec<(u64, SavedLineProgramRow)>, 29 } 30 31 #[derive(Debug, Eq, PartialEq)] 32 enum ReadLineProgramState { 33 SequenceEnded, 34 ReadSequence(DefinedFuncIndex), 35 IgnoreSequence, 36 } 37 38 pub(crate) fn clone_line_program<R>( 39 dwarf: &gimli::Dwarf<R>, 40 unit: &Unit<R, R::Offset>, 41 comp_name: Option<R>, 42 addr_tr: &AddressTransform, 43 out_encoding: gimli::Encoding, 44 out_strings: &mut write::StringTable, 45 ) -> Result<(write::LineProgram, DebugLineOffset, Vec<write::FileId>, u64), Error> 46 where 47 R: Reader, 48 { 49 if let Some(program) = unit.line_program.clone() { 50 let header = program.header(); 51 let offset = header.offset(); 52 let file_index_base = if header.version() < 5 { 1 } else { 0 }; 53 assert!(header.version() <= 5, "not supported 6"); 54 let line_encoding = LineEncoding { 55 minimum_instruction_length: header.minimum_instruction_length(), 56 maximum_operations_per_instruction: header.maximum_operations_per_instruction(), 57 default_is_stmt: header.default_is_stmt(), 58 line_base: header.line_base(), 59 line_range: header.line_range(), 60 }; 61 let out_comp_dir = match header.directory(0) { 62 Some(comp_dir) => clone_line_string( 63 dwarf.attr_string(unit, comp_dir)?, 64 gimli::DW_FORM_string, 65 out_strings, 66 )?, 67 None => write::LineString::String(Vec::new()), 68 }; 69 let out_comp_name = match comp_name { 70 Some(comp_name) => clone_line_string(comp_name, gimli::DW_FORM_strp, out_strings)?, 71 None => write::LineString::String(Vec::new()), 72 }; 73 74 let mut out_program = write::LineProgram::new( 75 out_encoding, 76 line_encoding, 77 out_comp_dir, 78 out_comp_name, 79 None, 80 ); 81 let mut dirs = Vec::new(); 82 dirs.push(out_program.default_directory()); 83 for dir_attr in header.include_directories() { 84 let dir_id = out_program.add_directory(clone_line_string( 85 dwarf.attr_string(unit, dir_attr.clone())?, 86 gimli::DW_FORM_string, 87 out_strings, 88 )?); 89 dirs.push(dir_id); 90 } 91 let mut files = Vec::new(); 92 // Since we are outputting DWARF-4, perform base change. 93 let directory_index_correction = if header.version() >= 5 { 1 } else { 0 }; 94 for file_entry in header.file_names() { 95 let dir_index = file_entry.directory_index() + directory_index_correction; 96 let dir_id = dirs[dir_index as usize]; 97 let file_id = out_program.add_file( 98 clone_line_string( 99 dwarf.attr_string(unit, file_entry.path_name())?, 100 gimli::DW_FORM_string, 101 out_strings, 102 )?, 103 dir_id, 104 None, 105 ); 106 files.push(file_id); 107 } 108 109 let mut rows = program.rows(); 110 let mut func_rows = Vec::new(); 111 let mut saved_rows: Vec<(u64, SavedLineProgramRow)> = Vec::new(); 112 let mut state = ReadLineProgramState::SequenceEnded; 113 while let Some((_header, row)) = rows.next_row()? { 114 if state == ReadLineProgramState::IgnoreSequence { 115 if row.end_sequence() { 116 state = ReadLineProgramState::SequenceEnded; 117 } 118 continue; 119 } 120 let saved_row = if row.end_sequence() { 121 let index = match state { 122 ReadLineProgramState::ReadSequence(index) => index, 123 _ => panic!(), 124 }; 125 saved_rows.sort_by_key(|r| r.0); 126 func_rows.push(FuncRows { 127 index, 128 sorted_rows: saved_rows, 129 }); 130 131 saved_rows = Vec::new(); 132 state = ReadLineProgramState::SequenceEnded; 133 SavedLineProgramRow::EndOfSequence 134 } else { 135 if state == ReadLineProgramState::SequenceEnded { 136 // Discard sequences for non-existent code. 137 if row.address() == 0 { 138 state = ReadLineProgramState::IgnoreSequence; 139 continue; 140 } 141 match addr_tr.find_func_index(row.address()) { 142 Some(index) => { 143 state = ReadLineProgramState::ReadSequence(index); 144 } 145 None => { 146 // Some non-existent address found. 147 state = ReadLineProgramState::IgnoreSequence; 148 continue; 149 } 150 } 151 } 152 SavedLineProgramRow::Normal { 153 address: row.address(), 154 op_index: row.op_index(), 155 file_index: row.file_index(), 156 line: row.line().map(|nonzero| nonzero.get()).unwrap_or(0), 157 column: match row.column() { 158 gimli::ColumnType::LeftEdge => 0, 159 gimli::ColumnType::Column(val) => val.get(), 160 }, 161 discriminator: row.discriminator(), 162 is_stmt: row.is_stmt(), 163 basic_block: row.basic_block(), 164 prologue_end: row.prologue_end(), 165 epilogue_begin: row.epilogue_begin(), 166 isa: row.isa(), 167 } 168 }; 169 saved_rows.push((row.address(), saved_row)); 170 } 171 172 for FuncRows { 173 index, 174 sorted_rows: saved_rows, 175 } in func_rows 176 { 177 let map = match addr_tr.map().get(index) { 178 Some(map) if map.len > 0 => map, 179 _ => { 180 continue; // no code generated 181 } 182 }; 183 let symbol = map.symbol; 184 let base_addr = map.offset; 185 out_program.begin_sequence(Some(write::Address::Symbol { symbol, addend: 0 })); 186 // TODO track and place function declaration line here 187 let mut last_address = None; 188 for addr_map in map.addresses.iter() { 189 let saved_row = match saved_rows.binary_search_by_key(&addr_map.wasm, |i| i.0) { 190 Ok(i) => Some(&saved_rows[i].1), 191 Err(i) => { 192 if i > 0 { 193 Some(&saved_rows[i - 1].1) 194 } else { 195 None 196 } 197 } 198 }; 199 if let Some(SavedLineProgramRow::Normal { 200 address, 201 op_index, 202 file_index, 203 line, 204 column, 205 discriminator, 206 is_stmt, 207 basic_block, 208 prologue_end, 209 epilogue_begin, 210 isa, 211 }) = saved_row 212 { 213 // Ignore duplicates 214 if Some(*address) != last_address { 215 let address_offset = if last_address.is_none() { 216 // Extend first entry to the function declaration 217 // TODO use the function declaration line instead 218 0 219 } else { 220 (addr_map.generated - base_addr) as u64 221 }; 222 out_program.row().address_offset = address_offset; 223 out_program.row().op_index = *op_index; 224 out_program.row().file = files[(file_index - file_index_base) as usize]; 225 out_program.row().line = *line; 226 out_program.row().column = *column; 227 out_program.row().discriminator = *discriminator; 228 out_program.row().is_statement = *is_stmt; 229 out_program.row().basic_block = *basic_block; 230 out_program.row().prologue_end = *prologue_end; 231 out_program.row().epilogue_begin = *epilogue_begin; 232 out_program.row().isa = *isa; 233 out_program.generate_row(); 234 last_address = Some(*address); 235 } 236 } 237 } 238 let end_addr = (map.offset + map.len) as u64; 239 out_program.end_sequence(end_addr); 240 } 241 Ok((out_program, offset, files, file_index_base)) 242 } else { 243 Err(TransformError("Valid line program not found").into()) 244 } 245 } 246 247 fn clone_line_string<R>( 248 value: R, 249 form: gimli::DwForm, 250 out_strings: &mut write::StringTable, 251 ) -> Result<write::LineString, Error> 252 where 253 R: Reader, 254 { 255 let content = value.to_string_lossy()?.into_owned(); 256 Ok(match form { 257 gimli::DW_FORM_strp => { 258 let id = out_strings.add(content); 259 write::LineString::StringRef(id) 260 } 261 gimli::DW_FORM_string => write::LineString::String(content.into()), 262 _ => bail!("DW_FORM_line_strp or other not supported"), 263 }) 264 } 265