use super::address_transform::AddressTransform; use super::attr::clone_attr_string; use super::{Reader, TransformError}; use anyhow::{Context, Error}; use gimli::{ write, DebugLine, DebugLineOffset, DebugLineStr, DebugStr, DebugStrOffsets, DebuggingInformationEntry, LineEncoding, Unit, }; use more_asserts::assert_le; use wasmtime_environ::entity::EntityRef; use wasmtime_environ::wasm::DefinedFuncIndex; #[derive(Debug)] enum SavedLineProgramRow { Normal { address: u64, op_index: u64, file_index: u64, line: u64, column: u64, discriminator: u64, is_stmt: bool, basic_block: bool, prologue_end: bool, epilogue_begin: bool, isa: u64, }, EndOfSequence(u64), } #[derive(Debug)] struct FuncRows { index: DefinedFuncIndex, sorted_rows: Vec<(u64, SavedLineProgramRow)>, } #[derive(Debug, Eq, PartialEq)] enum ReadLineProgramState { SequenceEnded, ReadSequence(DefinedFuncIndex), IgnoreSequence, } pub(crate) fn clone_line_program( unit: &Unit, root: &DebuggingInformationEntry, addr_tr: &AddressTransform, out_encoding: gimli::Encoding, debug_str: &DebugStr, debug_str_offsets: &DebugStrOffsets, debug_line_str: &DebugLineStr, debug_line: &DebugLine, out_strings: &mut write::StringTable, ) -> Result<(write::LineProgram, DebugLineOffset, Vec, u64), Error> where R: Reader, { let offset = match root.attr_value(gimli::DW_AT_stmt_list)? { Some(gimli::AttributeValue::DebugLineRef(offset)) => offset, _ => { return Err(TransformError("Debug line offset is not found").into()); } }; let comp_dir = root.attr_value(gimli::DW_AT_comp_dir)?; let comp_name = root.attr_value(gimli::DW_AT_name)?; let out_comp_dir = clone_attr_string( comp_dir.as_ref().context("comp_dir")?, gimli::DW_FORM_strp, unit, debug_str, debug_str_offsets, debug_line_str, out_strings, )?; let out_comp_name = clone_attr_string( comp_name.as_ref().context("comp_name")?, gimli::DW_FORM_strp, unit, debug_str, debug_str_offsets, debug_line_str, out_strings, )?; let program = debug_line.program( offset, unit.header.address_size(), comp_dir.and_then(|val| val.string_value(&debug_str)), comp_name.and_then(|val| val.string_value(&debug_str)), ); if let Ok(program) = program { let header = program.header(); let file_index_base = if header.version() < 5 { 1 } else { 0 }; assert_le!(header.version(), 5, "not supported 6"); let line_encoding = LineEncoding { minimum_instruction_length: header.minimum_instruction_length(), maximum_operations_per_instruction: header.maximum_operations_per_instruction(), default_is_stmt: header.default_is_stmt(), line_base: header.line_base(), line_range: header.line_range(), }; let mut out_program = write::LineProgram::new( out_encoding, line_encoding, out_comp_dir, out_comp_name, None, ); let mut dirs = Vec::new(); dirs.push(out_program.default_directory()); for dir_attr in header.include_directories() { let dir_id = out_program.add_directory(clone_attr_string( dir_attr, gimli::DW_FORM_string, unit, debug_str, debug_str_offsets, debug_line_str, out_strings, )?); dirs.push(dir_id); } let mut files = Vec::new(); // Since we are outputting DWARF-4, perform base change. let directory_index_correction = if header.version() >= 5 { 1 } else { 0 }; for file_entry in header.file_names() { let dir_index = file_entry.directory_index() + directory_index_correction; let dir_id = dirs[dir_index as usize]; let file_id = out_program.add_file( clone_attr_string( &file_entry.path_name(), gimli::DW_FORM_string, unit, debug_str, debug_str_offsets, debug_line_str, out_strings, )?, dir_id, None, ); files.push(file_id); } let mut rows = program.rows(); let mut func_rows = Vec::new(); let mut saved_rows: Vec<(u64, SavedLineProgramRow)> = Vec::new(); let mut state = ReadLineProgramState::SequenceEnded; while let Some((_header, row)) = rows.next_row()? { if state == ReadLineProgramState::IgnoreSequence { if row.end_sequence() { state = ReadLineProgramState::SequenceEnded; } continue; } let saved_row = if row.end_sequence() { let index = match state { ReadLineProgramState::ReadSequence(index) => index, _ => panic!(), }; saved_rows.sort_by_key(|r| r.0); func_rows.push(FuncRows { index, sorted_rows: saved_rows, }); saved_rows = Vec::new(); state = ReadLineProgramState::SequenceEnded; SavedLineProgramRow::EndOfSequence(row.address()) } else { if state == ReadLineProgramState::SequenceEnded { // Discard sequences for non-existent code. if row.address() == 0 { state = ReadLineProgramState::IgnoreSequence; continue; } match addr_tr.find_func_index(row.address()) { Some(index) => { state = ReadLineProgramState::ReadSequence(index); } None => { // Some non-existent address found. state = ReadLineProgramState::IgnoreSequence; continue; } } } SavedLineProgramRow::Normal { address: row.address(), op_index: row.op_index(), file_index: row.file_index(), line: row.line().unwrap_or(0), column: match row.column() { gimli::ColumnType::LeftEdge => 0, gimli::ColumnType::Column(val) => val, }, discriminator: row.discriminator(), is_stmt: row.is_stmt(), basic_block: row.basic_block(), prologue_end: row.prologue_end(), epilogue_begin: row.epilogue_begin(), isa: row.isa(), } }; saved_rows.push((row.address(), saved_row)); } for FuncRows { index, sorted_rows: saved_rows, } in func_rows { let map = match addr_tr.map().get(index) { Some(map) if map.len > 0 => map, _ => { continue; // no code generated } }; let symbol = index.index(); let base_addr = map.offset; out_program.begin_sequence(Some(write::Address::Symbol { symbol, addend: 0 })); // TODO track and place function declaration line here let mut last_address = None; for addr_map in map.addresses.iter() { let saved_row = match saved_rows.binary_search_by_key(&addr_map.wasm, |i| i.0) { Ok(i) => Some(&saved_rows[i].1), Err(i) => { if i > 0 { Some(&saved_rows[i - 1].1) } else { None } } }; if let Some(SavedLineProgramRow::Normal { address, op_index, file_index, line, column, discriminator, is_stmt, basic_block, prologue_end, epilogue_begin, isa, }) = saved_row { // Ignore duplicates if Some(*address) != last_address { let address_offset = if last_address.is_none() { // Extend first entry to the function declaration // TODO use the function declaration line instead 0 } else { (addr_map.generated - base_addr) as u64 }; out_program.row().address_offset = address_offset; out_program.row().op_index = *op_index; out_program.row().file = files[(file_index - file_index_base) as usize]; out_program.row().line = *line; out_program.row().column = *column; out_program.row().discriminator = *discriminator; out_program.row().is_statement = *is_stmt; out_program.row().basic_block = *basic_block; out_program.row().prologue_end = *prologue_end; out_program.row().epilogue_begin = *epilogue_begin; out_program.row().isa = *isa; out_program.generate_row(); last_address = Some(*address); } } } let end_addr = (map.offset + map.len - 1) as u64; out_program.end_sequence(end_addr); } Ok((out_program, offset, files, file_index_base)) } else { Err(TransformError("Valid line program not found").into()) } }