use crate::gdb::{response::*, Address, BreakPoint, BreakpointOperationError, SrcPosition};
use crate::gdbmi::{
commands::{BreakPointLocation, BreakPointNumber, DisassembleMode, MiCommand},
output::{JsonValue, Object, ResultClass},
ExecuteError,
};
use crate::Context;
use log::warn;
use std::{
collections::HashSet,
fs, io,
ops::Range,
path::{Path, PathBuf},
};
use unsegen::{
base::{basic_types::*, Color, Cursor, GraphemeCluster, StyleModifier, Window},
container::Container,
input::{Input, Key, ScrollBehavior},
widget::{
text_width, ColDemand, Demand, Demand2D, HLayout, RenderingHints, RowDemand, VLayout,
Widget, WidgetExt,
},
};
use unsegen_pager::{
LineDecorator, Pager, PagerContent, PagerError, PagerLine, SyntectHighlighter,
};
use unsegen_pager::{SyntaxSet, Theme};
#[derive(Debug)]
pub enum PagerShowError {
CouldNotOpenFile(PathBuf, io::Error),
}
#[derive(Clone)]
struct AssemblyDebugLocation {
func_name: String,
offset: usize,
}
impl AssemblyDebugLocation {
fn try_from_value(val: &JsonValue) -> Option<Self> {
let func_name = val["func-name"].as_str()?;
let offset = val["offset"].as_str()?.parse::<usize>().ok()?;
Some(AssemblyDebugLocation {
func_name: func_name.to_owned(),
offset,
})
}
}
#[derive(Clone)]
struct AssemblyLine {
content: String,
address: Address,
src_position: Option<SrcPosition>,
debug_location: Option<AssemblyDebugLocation>,
}
impl AssemblyLine {
const fn new(
content: String,
address: Address,
src_position: Option<SrcPosition>,
debug_location: Option<AssemblyDebugLocation>,
) -> Self {
AssemblyLine {
content,
address,
src_position,
debug_location,
}
}
}
impl PagerLine for AssemblyLine {
fn get_content(&self) -> &str {
&self.content
}
}
struct AssemblyDecorator {
stop_position: Option<Address>,
breakpoint_addresses: HashSet<Address>,
}
impl AssemblyDecorator {
fn new<'a, I: Iterator<Item = &'a BreakPoint>>(
address_range: Range<Address>,
stop_position: Option<Address>,
breakpoints: I,
) -> Self {
let addresses = breakpoints
.filter_map(|bp| {
bp.address.and_then(|addr| {
if bp.enabled && address_range.start <= addr && addr < address_range.end {
Some(addr)
} else {
None
}
})
})
.collect();
let stop_position = if let Some(p) = stop_position {
if address_range.start <= p && p < address_range.end {
Some(p)
} else {
None
}
} else {
None
};
AssemblyDecorator {
stop_position,
breakpoint_addresses: addresses,
}
}
}
impl LineDecorator for AssemblyDecorator {
type Line = AssemblyLine;
fn horizontal_space_demand<'a, 'b: 'a>(
&'a self,
lines: impl DoubleEndedIterator<Item = (LineIndex, &'b Self::Line)> + 'b,
) -> ColDemand {
let max_space = lines
.last()
.map(|(_, l)| text_width(format!(" 0x{:x} ", l.address.0).as_str()))
.unwrap_or_else(|| Width::new(0).unwrap());
Demand::exact(max_space)
}
fn decorate(
&self,
line: &Self::Line,
current_line: LineIndex,
active_line: LineIndex,
mut window: Window,
) {
let width = window.get_width();
let mut cursor = Cursor::new(&mut window).position(ColIndex::new(0), RowIndex::new(0));
let at_stop_position = self
.stop_position
.map(|p| p == line.address)
.unwrap_or(false);
let at_breakpoint_position = self.breakpoint_addresses.contains(&line.address);
let (right_border, style_modifier) = match (at_stop_position, at_breakpoint_position) {
(true, true) => ('▶', StyleModifier::new().fg_color(Color::Red).bold(true)),
(true, false) => ('▶', StyleModifier::new().fg_color(Color::Green).bold(true)),
(false, true) => ('●', StyleModifier::new().fg_color(Color::Red)),
(false, false) => (' ', StyleModifier::new()),
};
cursor.set_style_modifier(style_modifier);
use std::fmt::Write;
if let (false, Some(offset)) = (
current_line == active_line,
line.debug_location
.iter()
.map(|l| l.offset)
.find(|&offset| offset != 0),
) {
let formatted_offset = format!("<+{}>", offset);
write!(
cursor,
"{:>width$}{}",
formatted_offset,
right_border,
width = (width - 1).positive_or_zero().into()
)
.unwrap();
} else {
write!(
cursor,
" 0x{:0>width$x}{}",
line.address.0,
right_border,
width = (width - 4).positive_or_zero().into()
)
.unwrap();
}
}
}
pub struct AssemblyView<'a> {
highlighting_theme: &'a Theme,
syntax_set: SyntaxSet,
pager: Pager<AssemblyLine, AssemblyDecorator>,
last_stop_position: Option<Address>,
}
#[derive(Debug, derive_more::From)]
enum GotoError {
NoLastStopPosition,
MismatchedPagerContent,
PagerError(PagerError),
}
impl<'a> AssemblyView<'a> {
pub fn new(highlighting_theme: &'a Theme) -> Self {
AssemblyView {
highlighting_theme,
syntax_set: SyntaxSet::load_defaults_nonewlines(),
pager: Pager::new(),
last_stop_position: None,
}
}
fn set_last_stop_position(&mut self, pos: Address) {
self.last_stop_position = Some(pos);
}
fn go_to_address(&mut self, pos: Address) -> Result<(), GotoError> {
Ok(self.pager.go_to_line_if(|_, line| line.address == pos)?)
}
fn go_to_first_applicable_line<L: Into<LineNumber>>(
&mut self,
file: &Path,
line: L,
) -> Result<(), GotoError> {
let line: LineNumber = line.into();
Ok(self.pager.go_to_line_if(|_, l| {
if let Some(ref src_position) = l.src_position {
src_position.file == file && src_position.line == line
} else {
false
}
})?)
}
fn go_to_last_stop_position(&mut self) -> Result<(), GotoError> {
if let Some(address) = self.last_stop_position {
self.go_to_address(address)
} else {
Err(GotoError::NoLastStopPosition)
}
}
fn update_decoration(&mut self, p: &mut Context) {
if let Some(ref mut content) = self.pager.content_mut() {
let first_line_address = content.view_line(LineIndex::new(0)).map(|l| l.address);
if let Some(min_address) = first_line_address {
let max_address = {
content
.view(LineIndex::new(0)..)
.last()
.expect("We know we have at least one line")
.1
.address
};
content.set_decorator(AssemblyDecorator::new(
min_address..max_address,
self.last_stop_position,
p.gdb.breakpoints.values(),
));
}
}
}
fn show_lines(&mut self, lines: Vec<AssemblyLine>, p: &mut Context) {
if lines.is_empty() {
return; }
let min_address = lines.first().expect("We know lines is not empty").address;
let max_address = lines.last().expect("We know lines is not empty").address + 1;
let syntax = self
.syntax_set
.find_syntax_by_extension("s")
.unwrap_or_else(|| self.syntax_set.find_syntax_plain_text());
let highlighter = SyntectHighlighter::new(syntax, self.highlighting_theme);
self.pager.load(
PagerContent::from_lines(lines)
.with_highlighter(&highlighter)
.with_decorator(AssemblyDecorator::new(
min_address..max_address,
self.last_stop_position,
p.gdb.breakpoints.values(),
)),
);
}
fn get_instructions(disass_results: &Object) -> Result<Vec<AssemblyLine>, GDBResponseError> {
if let JsonValue::Array(line_objs) = &disass_results["asm_insns"] {
let mut lines = Vec::<AssemblyLine>::new();
for line_obj in line_objs {
let line = LineNumber::new(
get_str(line_obj, "line")?
.parse::<usize>()
.map_err(|_| GDBResponseError::Other("Malformed line".into()))?,
);
let file = get_str(line_obj, "fullname")?;
let src_pos = Some(SrcPosition::new(PathBuf::from(file), line));
for tuple in line_obj["line_asm_insn"].members() {
let instruction = get_str(tuple, "inst")?;
let address = get_addr(tuple, "address")?;
lines.push(AssemblyLine::new(
instruction.to_owned(),
address,
src_pos.clone(),
AssemblyDebugLocation::try_from_value(tuple),
));
}
}
lines.sort_by_key(|l| l.address);
Ok(lines)
} else {
Err(GDBResponseError::MissingField(
"asm_insns",
JsonValue::Object(disass_results.clone()),
))
}
}
fn show_file<P: AsRef<Path>, L: Into<LineNumber>>(
&mut self,
file: P,
line: L,
p: &mut Context,
) -> Result<(), DisassembleError> {
let line_u: usize = line.into().into();
let disass_results = p
.gdb
.mi
.execute(MiCommand::data_disassemble_file(
file.as_ref(),
line_u,
None,
DisassembleMode::MixedSourceAndDisassembly,
))?
.results;
let lines = Self::get_instructions(&disass_results)?;
self.show_lines(lines, p);
Ok(())
}
fn show_address(
&mut self,
address_start: Address,
address_end: Address,
p: &mut Context,
) -> Result<(), DisassembleError> {
let line_objs = disassemble_address(address_start, address_end, p)?;
let mut lines = Vec::<AssemblyLine>::new();
for line_tuple in line_objs {
let instruction = get_str(&line_tuple, "inst")?;
let address = get_addr(&line_tuple, "address")?;
lines.push(AssemblyLine::new(
instruction.to_owned(),
address,
None,
AssemblyDebugLocation::try_from_value(&line_tuple),
));
}
self.show_lines(lines, p);
Ok(())
}
fn toggle_breakpoint(&self, p: &mut Context) {
if let Some(line) = self.pager.current_line() {
let active_bps: Vec<BreakPointNumber> = p
.gdb
.breakpoints
.values()
.filter_map(|bp| {
if let Some(ref address) = bp.address {
if *address == line.address {
Some(bp.number)
} else {
None
}
} else {
None
}
})
.collect();
if active_bps.is_empty() {
match p
.gdb
.insert_breakpoint(BreakPointLocation::Address(line.address.0))
{
Ok(()) => {}
Err(BreakpointOperationError::Busy) => {
p.log("Cannot insert breakpoint: Gdb is busy.");
}
Err(BreakpointOperationError::ExecutionError(msg)) => {
p.log(format!("Cannot insert breakpoint: {}", msg));
}
}
} else {
match p.gdb.delete_breakpoints(active_bps.into_iter()) {
Ok(()) => {}
Err(BreakpointOperationError::Busy) => {
p.log("Cannot remove breakpoint: Gdb is busy.");
}
Err(BreakpointOperationError::ExecutionError(msg)) => {
p.log(format!("Cannot remove breakpoint: {}", msg));
}
}
}
}
}
fn event(&mut self, event: Input, p: &mut Context) -> Option<Input> {
event
.chain(
ScrollBehavior::new(&mut self.pager)
.forwards_on(Key::Down)
.forwards_on(Key::Char('j'))
.backwards_on(Key::Up)
.backwards_on(Key::Char('k'))
.to_beginning_on(Key::Home)
.to_end_on(Key::End),
)
.chain((Key::Char(' '), || self.toggle_breakpoint(p)))
.finish()
}
}
struct SourceDecorator {
stop_position: Option<LineNumber>,
breakpoint_lines: HashSet<LineNumber>,
}
impl SourceDecorator {
fn new<'a, I: Iterator<Item = &'a BreakPoint>>(
file: &Path,
stop_position: Option<LineNumber>,
breakpoints: I,
) -> Self {
let addresses = breakpoints
.filter_map(|bp| {
bp.src_pos.clone().and_then(|pos| {
if bp.enabled && pos.file == file {
Some(pos.line)
} else {
None
}
})
})
.collect();
SourceDecorator {
stop_position,
breakpoint_lines: addresses,
}
}
}
impl LineDecorator for SourceDecorator {
type Line = String;
fn horizontal_space_demand<'a, 'b: 'a>(
&'a self,
lines: impl DoubleEndedIterator<Item = (LineIndex, &'b Self::Line)> + 'b,
) -> ColDemand {
let max_space = lines
.last()
.map(|(i, _)| text_width(format!(" {} ", i).as_str()))
.unwrap_or_else(|| Width::new(0).unwrap());
Demand::exact(max_space)
}
fn decorate(
&self,
_: &Self::Line,
current_index: LineIndex,
_active_index: LineIndex,
mut window: Window,
) {
let width = (window.get_width() - 2).positive_or_zero();
let line_number = LineNumber::from(current_index);
let mut cursor = Cursor::new(&mut window).position(ColIndex::new(0), RowIndex::new(0));
let at_stop_position = self
.stop_position
.map(|p| p == current_index.into())
.unwrap_or(false);
let at_breakpoint_position = self.breakpoint_lines.contains(¤t_index.into());
let (right_border, style_modifier) = match (at_stop_position, at_breakpoint_position) {
(true, true) => ('▶', StyleModifier::new().fg_color(Color::Red).bold(true)),
(true, false) => ('▶', StyleModifier::new().fg_color(Color::Green).bold(true)),
(false, true) => ('●', StyleModifier::new().fg_color(Color::Red)),
(false, false) => (' ', StyleModifier::new()),
};
cursor.set_style_modifier(style_modifier);
use std::fmt::Write;
write!(
cursor,
" {:width$}{}",
line_number,
right_border,
width = width.into()
)
.unwrap();
}
}
#[derive(Clone)]
struct FileInfo {
path: PathBuf,
modified: std::time::SystemTime,
}
pub struct SourceView<'a> {
highlighting_theme: &'a Theme,
syntax_set: SyntaxSet,
pager: Pager<String, SourceDecorator>,
file_info: Option<FileInfo>,
last_stop_position: Option<SrcPosition>,
}
macro_rules! current_file_and_content_mut {
($x:expr) => {
match (&$x.file_info, &mut $x.pager.content_mut()) {
(&Some(ref file_info), &mut Some(ref mut content)) => Some((&file_info.path, content)),
(&None, &mut None) => None,
(&Some(_), &mut None) => panic!("Pager has file path, but no content"),
(&None, &mut Some(_)) => panic!("Pager has content, but no file path"),
}
};
}
impl<'a> SourceView<'a> {
pub fn new(highlighting_theme: &'a Theme) -> Self {
SourceView {
highlighting_theme,
syntax_set: SyntaxSet::load_defaults_nonewlines(),
pager: Pager::new(),
file_info: None,
last_stop_position: None,
}
}
fn set_last_stop_position<P: AsRef<Path>>(&mut self, file: P, pos: LineNumber) {
self.last_stop_position = Some(SrcPosition::new(file.as_ref().to_path_buf(), pos));
}
fn go_to_line<L: Into<LineNumber>>(&mut self, line: L) -> Result<(), GotoError> {
Ok(self.pager.go_to_line(line.into())?)
}
fn go_to_last_stop_position(&mut self) -> Result<(), GotoError> {
let line = if let Some(ref file_info) = self.file_info {
if let Some(ref src_pos) = self.last_stop_position {
if src_pos.file == file_info.path {
src_pos.line
} else {
return Err(GotoError::MismatchedPagerContent);
}
} else {
return Err(GotoError::NoLastStopPosition);
}
} else {
return Err(GotoError::from(PagerError::NoContent));
};
self.go_to_line(line)
}
fn get_last_line_number_for<P: AsRef<Path>>(&self, file: P) -> Option<LineNumber> {
self.last_stop_position.clone().and_then(|last_src_pos| {
if file.as_ref() == last_src_pos.file {
Some(last_src_pos.line)
} else {
None
}
})
}
fn update_decoration(&mut self, p: &mut Context) {
if let Some((file_path, content)) = current_file_and_content_mut!(self) {
let last_line_number = self.last_stop_position.clone().and_then(|last_src_pos| {
if last_src_pos.file == *file_path {
Some(last_src_pos.line)
} else {
None
}
});
content.set_decorator(SourceDecorator::new(
file_path,
last_line_number,
p.gdb.breakpoints.values(),
));
}
}
fn need_to_load_file(&self, path: &Path) -> bool {
if let Some(ref loaded_file_info) = self.file_info {
if loaded_file_info.path != path {
return true;
}
if let Ok(modified_new) = fs::metadata(path).and_then(|m| m.modified()) {
modified_new > loaded_file_info.modified
} else {
true
}
} else {
true
}
}
fn show<P: AsRef<Path>>(&mut self, path: P, p: &mut Context) -> Result<(), PagerShowError> {
if self.need_to_load_file(path.as_ref()) {
let path_ref = path.as_ref();
self.load(path_ref, p.gdb.breakpoints.values())
.map_err(|e| PagerShowError::CouldNotOpenFile(path_ref.to_path_buf(), e))?;
} else {
let last_line_number = self.get_last_line_number_for(path.as_ref());
if let Some(ref mut content) = self.pager.content_mut() {
content.set_decorator(SourceDecorator::new(
path.as_ref(),
last_line_number,
p.gdb.breakpoints.values(),
));
}
}
Ok(())
}
fn reload(&mut self, p: &mut Context) -> Result<(), PagerShowError> {
if let Some(i) = self.file_info.clone() {
self.show(i.path, p)?;
}
Ok(())
}
fn content_is_stale(&self) -> bool {
if let Some(i) = &self.file_info {
self.need_to_load_file(&i.path)
} else {
true
}
}
fn load<'b, P: AsRef<Path>, I: Iterator<Item = &'b BreakPoint>>(
&mut self,
path: P,
breakpoints: I,
) -> io::Result<()> {
let pager_content = PagerContent::from_file(path.as_ref())?;
let syntax = self
.syntax_set
.find_syntax_for_file(path.as_ref())
.expect("file IS openable, see pager content")
.unwrap_or_else(|| self.syntax_set.find_syntax_plain_text());
let last_line_number = self.get_last_line_number_for(path.as_ref());
let highlighter = SyntectHighlighter::new(syntax, self.highlighting_theme);
self.pager
.load(pager_content.with_highlighter(&highlighter).with_decorator(
SourceDecorator::new(path.as_ref(), last_line_number, breakpoints),
));
self.file_info = Some(FileInfo {
path: path.as_ref().to_owned(),
modified: fs::metadata(path)?.modified()?,
});
Ok(())
}
fn current_line_number(&self) -> LineNumber {
self.pager.current_line_index().into()
}
fn current_file(&self) -> Option<&Path> {
if let Some(ref file_info) = self.file_info {
Some(&file_info.path)
} else {
None
}
}
fn toggle_breakpoint(&self, p: &mut Context) {
let line = self.current_line_number();
if let Some(path) = self.current_file() {
let active_bps: Vec<BreakPointNumber> = p
.gdb
.breakpoints
.values()
.filter_map(|bp| {
if let Some(ref src_pos) = bp.src_pos {
if src_pos.file == path && src_pos.line == line {
Some(bp.number)
} else {
None
}
} else {
None
}
})
.collect();
if active_bps.is_empty() {
if p.gdb
.insert_breakpoint(BreakPointLocation::Line(path, line.into()))
.is_err()
{
p.log("Cannot insert breakpoint: Gdb is busy.");
}
} else if p.gdb.delete_breakpoints(active_bps.into_iter()).is_err() {
p.log("Cannot remove breakpoint: Gdb is busy.");
}
}
}
fn event(&mut self, event: Input, p: &mut Context) -> Option<Input> {
event
.chain(
ScrollBehavior::new(&mut self.pager)
.forwards_on(Key::Down)
.forwards_on(Key::Char('j'))
.backwards_on(Key::Up)
.backwards_on(Key::Char('k'))
.to_beginning_on(Key::Home)
.to_end_on(Key::End),
)
.chain((Key::Char(' '), || self.toggle_breakpoint(p)))
.finish()
}
}
#[derive(Clone, PartialEq)]
enum DisplayMode {
Source,
Assembly,
SideBySide,
Message(String),
}
#[derive(Clone, PartialEq)]
enum SrcContentState {
Available,
Unavailable,
NotYetLoaded(PathBuf),
}
#[derive(Clone, PartialEq)]
enum AsmContentState {
Available,
Unavailable,
NotYetLoadedFile(PathBuf, LineIndex),
NotYetLoadedAddr(Address, Address),
}
#[derive(Default)]
struct StackInfo {
stack_level: Option<u64>,
stack_depth: Option<u64>,
file_path: Option<PathBuf>,
function: Option<String>,
}
impl<'a> Widget for &'a StackInfo {
fn space_demand(&self) -> Demand2D {
Demand2D {
width: Demand::at_least(Width::new(1).unwrap()),
height: Demand::exact(Height::new(1).unwrap()),
}
}
fn draw(&self, mut window: Window, _hints: RenderingHints) {
use std::fmt::Write;
let width = window.get_width();
let mut cursor = Cursor::new(&mut window).style_modifier(StyleModifier::new().bold(true));
let _ = write!(cursor, "[");
if let Some(l) = self.stack_level {
let _ = write!(cursor, "{}", l);
} else {
let _ = write!(cursor, "?");
}
let _ = write!(cursor, "/");
if let Some(l) = self.stack_depth {
let _ = write!(cursor, "{}", l);
} else {
let _ = write!(cursor, "?");
}
let _ = write!(cursor, "] ");
if let Some(f) = &self.function {
let _ = write!(cursor, "{}", f);
} else {
let _ = write!(cursor, "?");
}
{
let mut cursor = cursor.save().style_modifier();
cursor.set_style_modifier(StyleModifier::new().bold(false));
let _ = write!(cursor, " @ ");
}
if let Some(f) = &self.file_path {
let path_str = f.to_string_lossy();
let remaining_space =
(width.raw_value() as usize).saturating_sub(cursor.get_col().raw_value() as _);
if remaining_space >= text_width(path_str.as_ref()).raw_value() as _ {
let _ = write!(cursor, "{}", path_str);
} else {
if let Some(n) = f.file_name() {
let _ = write!(cursor, "{}", n.to_string_lossy());
} else {
let _ = write!(cursor, "?");
}
}
} else {
let _ = write!(cursor, "?");
}
}
}
#[derive(Clone, Debug, PartialEq, derive_more::From)]
#[allow(clippy::upper_case_acronyms)]
enum DisassembleError {
GDB(GDBResponseError),
Other(String),
}
impl From<ExecuteError> for DisassembleError {
fn from(e: ExecuteError) -> Self {
GDBResponseError::Execution(e).into()
}
}
fn disassemble_address(
address_start: Address,
address_end: Address,
p: &mut Context,
) -> Result<Vec<JsonValue>, DisassembleError> {
let mut disass_results = match p.gdb.mi.execute(MiCommand::data_disassemble_address(
address_start.0,
address_end.0,
DisassembleMode::DisassemblyOnly,
)) {
Ok(o) => {
if o.class == ResultClass::Error {
return Err(DisassembleError::Other(
o.results["msg"].as_str().unwrap_or("unknown").to_owned(),
));
}
o.results
}
Err(e) => {
return Err(GDBResponseError::Execution(e).into());
}
};
if let JsonValue::Array(line_objs) = disass_results["asm_insns"].take() {
let mut line_objs = line_objs
.into_iter()
.map(|l| {
let addr = get_addr(&l, "address")?;
Ok((addr, l))
})
.collect::<Result<Vec<(Address, JsonValue)>, DisassembleError>>()?;
line_objs.sort_by_key(|(a, _)| *a);
Ok(line_objs.into_iter().map(|(_, o)| o).collect::<Vec<_>>())
} else {
Err(
GDBResponseError::MissingField("asm_insns", JsonValue::Object(disass_results.clone()))
.into(),
)
}
}
pub struct CodeWindow<'a> {
src_view: SourceView<'a>,
asm_view: AssemblyView<'a>,
preferred_mode: DisplayMode,
src_state: SrcContentState,
asm_state: AsmContentState,
last_bp_update: std::time::Instant,
stack_info: StackInfo,
}
impl<'a> CodeWindow<'a> {
pub fn new(highlighting_theme: &'a Theme, welcome_msg: &'static str) -> Self {
CodeWindow {
src_view: SourceView::new(highlighting_theme),
asm_view: AssemblyView::new(highlighting_theme),
preferred_mode: DisplayMode::Message(welcome_msg.to_owned()),
src_state: SrcContentState::Unavailable,
asm_state: AsmContentState::Unavailable,
last_bp_update: std::time::Instant::now(),
stack_info: Default::default(),
}
}
fn available_display_mode(&self) -> DisplayMode {
match (&self.preferred_mode, &self.src_state, &self.asm_state) {
(DisplayMode::Message(msg), _, _) => DisplayMode::Message(msg.clone()),
(DisplayMode::Source, SrcContentState::Available, _) => DisplayMode::Source,
(DisplayMode::Source, _, AsmContentState::Available) => DisplayMode::Assembly,
(DisplayMode::Assembly, _, AsmContentState::Available) => DisplayMode::Assembly,
(DisplayMode::Assembly, SrcContentState::Available, _) => DisplayMode::Source,
(DisplayMode::SideBySide, SrcContentState::Available, AsmContentState::Available) => {
DisplayMode::SideBySide
}
(DisplayMode::SideBySide, SrcContentState::Available, _) => DisplayMode::Source,
(DisplayMode::SideBySide, _, AsmContentState::Available) => DisplayMode::Assembly,
(_, _, _) => DisplayMode::Message("Neither source nor assembly available!".to_owned()),
}
}
fn try_load_source_content(&mut self, p: &mut Context) -> Result<(), PagerShowError> {
match self.src_state.clone() {
SrcContentState::NotYetLoaded(path) => {
let ret = self.src_view.show(path, p);
if ret.is_ok() {
self.src_state = SrcContentState::Available;
} else {
self.src_state = SrcContentState::Unavailable;
}
ret
}
_ => {
if self.src_view.content_is_stale() {
self.src_view.reload(p)?;
}
Ok(())
}
}
}
fn try_load_asm_content(&mut self, p: &mut Context) -> Result<(), DisassembleError> {
match self.asm_state.clone() {
AsmContentState::NotYetLoadedFile(path, line) => {
let ret = self.asm_view.show_file(path, line, p);
if ret.is_ok() {
self.asm_state = AsmContentState::Available;
} else {
self.asm_state = AsmContentState::Unavailable;
}
ret
}
AsmContentState::NotYetLoadedAddr(begin, end) => {
let ret = self.asm_view.show_address(begin, end, p);
if ret.is_ok() {
self.asm_state = AsmContentState::Available;
} else {
self.asm_state = AsmContentState::Unavailable;
}
ret
}
_ => Ok(()),
}
}
fn try_load_active_content(&mut self, p: &mut Context) {
let try_load_src = |s: &mut Self, p: &mut Context| {
if let Err(e) = s.try_load_source_content(p) {
warn!("Failed to load file: {:?}", e);
}
};
let try_load_asm = |s: &mut Self, p: &mut Context| match s.try_load_asm_content(p) {
Err(DisassembleError::GDB(GDBResponseError::Execution(ExecuteError::Busy))) => {
p.log("Cannot disassemble: Gdb is busy.");
}
Err(e) => warn!("Failed to load assembly: {:?}", e),
Ok(_) => {}
};
match self.preferred_mode {
DisplayMode::SideBySide => {
try_load_src(self, p);
try_load_asm(self, p);
}
DisplayMode::Assembly => {
try_load_asm(self, p);
if self.asm_state == AsmContentState::Unavailable {
try_load_src(self, p);
}
}
DisplayMode::Source => {
try_load_src(self, p);
if self.src_state == SrcContentState::Unavailable {
try_load_asm(self, p);
}
}
DisplayMode::Message(_) => {}
}
}
fn find_function_range(at: Address, p: &mut Context) -> Result<(Address, Address), ()> {
let first_lines = disassemble_address(at, at + 16, p).map_err(|_| ())?;
let current = first_lines.first().ok_or(())?;
let asm_debug_location = AssemblyDebugLocation::try_from_value(current).ok_or(())?;
let begin = at - asm_debug_location.offset;
let block_size = 128;
let mut current = at;
let func_change_block = loop {
let current_block_lines =
disassemble_address(current, current + block_size, p).map_err(|_| ())?;
{
let penultimate_index = current_block_lines.len().checked_sub(2).ok_or(())?;
let penultimate = current_block_lines
.get(penultimate_index)
.expect("We know penulatimate_index is valid");
if let Some(penultimate_func_name) = penultimate["func-name"].as_str() {
if penultimate_func_name == asm_debug_location.func_name {
current = get_addr(penultimate, "address").map_err(|_| ())?;
continue;
}
}
}
break current_block_lines;
};
for line in func_change_block {
if line["func-name"] != asm_debug_location.func_name {
let end = get_addr(&line, "address").map_err(|_| ())?;
return Ok((begin, end));
}
}
unreachable!("func_change_block has to contain changing line");
}
fn find_valid_address_range(
at: Address,
approx_byte_size: usize,
p: &mut Context,
) -> Result<(Address, Address), DisassembleError> {
let block_lines = disassemble_address(at, at + approx_byte_size, p)?;
let penultimate_index = block_lines
.len()
.checked_sub(2)
.ok_or_else(|| DisassembleError::Other("Not enough lines".to_owned()))?;
let penultimate = block_lines
.get(penultimate_index)
.ok_or_else(|| DisassembleError::Other("Not enough lines".to_owned()))?;
let end_address = get_addr(penultimate, "address")?;
Ok((at, end_address))
}
pub fn show_file(&mut self, file: String, line: LineNumber, p: &mut Context) {
let mut object = Object::new();
object.insert("fullname", JsonValue::String(file));
object.insert("line", JsonValue::String(line.to_string()));
self.show_frame(&object, p);
}
pub fn show_frame(&mut self, frame: &Object, p: &mut Context) {
if let DisplayMode::Message(_) = self.preferred_mode {
self.preferred_mode = DisplayMode::Source;
}
self.src_state = SrcContentState::Unavailable;
self.asm_state = AsmContentState::Unavailable;
self.stack_info.stack_level = p.gdb.get_stack_level().ok();
self.stack_info.stack_depth = p.gdb.get_stack_depth().ok();
self.stack_info.file_path = frame["fullname"].as_str().map(PathBuf::from);
self.stack_info.function = frame["func"].as_str().map(|s| s.to_owned());
if let Some(path) = frame["fullname"].as_str() {
let path = PathBuf::from(path);
self.src_state = match self.src_view.current_file() {
Some(f) if f == path => SrcContentState::Available,
_ => SrcContentState::NotYetLoaded(path.clone()),
};
match get_u64_obj(frame, "line") {
Ok(line) => {
let line = LineNumber::new(line as usize);
self.src_view.set_last_stop_position(path.clone(), line);
self.asm_state = if self
.asm_view
.go_to_first_applicable_line(&path, line)
.is_ok()
{
AsmContentState::Available
} else {
AsmContentState::NotYetLoadedFile(path, line.into())
};
match get_addr_obj(frame, "addr") {
Ok(address) => self.asm_view.set_last_stop_position(address),
Err(e) => warn!("Failed get address from frame: {:?}", e),
}
}
Err(e) => warn!("Failed get line from frame: {:?}", e),
}
};
if self.asm_state == AsmContentState::Unavailable {
match get_addr_obj(frame, "addr") {
Ok(address) => {
if self.asm_view.go_to_address(address).is_ok() {
self.asm_state = AsmContentState::Available;
} else {
match Self::find_function_range(address, p)
.or_else(|_| Self::find_valid_address_range(address, 128, p))
{
Ok((begin, end)) => {
self.asm_state = AsmContentState::NotYetLoadedAddr(begin, end)
}
Err(e) => {
warn!("Failed to disassemble from address {}: {:?}", address, e)
}
};
}
self.asm_view.set_last_stop_position(address);
}
Err(e) => warn!("Failed get address from frame: {:?}", e),
}
}
self.try_load_active_content(p);
let _ = self.asm_view.go_to_last_stop_position();
let _ = self.src_view.go_to_last_stop_position();
self.asm_view.update_decoration(p);
self.src_view.update_decoration(p);
}
fn toggle_mode(&mut self, p: &mut Context) {
let mut sync_asm_to_src = false;
let prev_mode = self.preferred_mode.clone();
self.preferred_mode = match prev_mode {
DisplayMode::Assembly => DisplayMode::Source,
DisplayMode::SideBySide => DisplayMode::Assembly,
DisplayMode::Source => {
sync_asm_to_src = true;
DisplayMode::SideBySide
}
DisplayMode::Message(ref m) => DisplayMode::Message(m.clone()),
};
self.try_load_active_content(p);
if self.available_display_mode() == prev_mode {
self.preferred_mode = prev_mode;
} else if sync_asm_to_src {
if let Some(path) = self.src_view.current_file() {
if self
.asm_view
.go_to_first_applicable_line(path, self.src_view.current_line_number())
.is_err()
&& self
.asm_view
.show_file(path, self.src_view.current_line_number(), p)
.is_ok()
{
let _ = self
.asm_view
.go_to_first_applicable_line(path, self.src_view.current_line_number());
}
}
}
}
fn try_switch_stackframe(&mut self, p: &mut Context, up: bool) -> Result<(), GDBResponseError> {
let level = p.gdb.get_stack_level()?;
let new_level = if up {
let depth = p.gdb.get_stack_depth()?;
(level + 1).min(depth.saturating_sub(1))
} else {
level.saturating_sub(1)
};
if level != new_level {
p.gdb.mi.execute_later(MiCommand::select_frame(new_level));
match p.gdb.mi.execute(MiCommand::stack_info_frame(None)) {
Ok(o) => {
if o.class == ResultClass::Done {
if let JsonValue::Object(ref frame) = o.results["frame"] {
self.show_frame(frame, p);
} else {
return Err(GDBResponseError::MissingField(
"frame",
JsonValue::Object(o.results.clone()),
));
}
} else {
return Err(GDBResponseError::Other(format!(
"Unexpected result class: {:?}",
o.class
)));
}
}
Err(_) => return Ok(()), };
}
Ok(())
}
fn switch_stackframe(&mut self, p: &mut Context, up: bool) {
match self.try_switch_stackframe(p, up) {
Ok(_) => {}
Err(e) => {
warn!("Failed to switch stackframe: {:?}", e);
}
}
}
pub fn update_after_event(&mut self, p: &mut Context) {
if p.gdb.breakpoints.last_change > self.last_bp_update {
self.asm_view.update_decoration(p);
self.src_view.update_decoration(p);
self.last_bp_update = p.gdb.breakpoints.last_change;
}
}
}
impl<'a> Container<Context> for CodeWindow<'a> {
fn input(&mut self, input: Input, p: &mut Context) -> Option<Input> {
input
.chain((Key::Char('d'), || self.toggle_mode(p)))
.chain((Key::PageUp, || self.switch_stackframe(p, true)))
.chain((Key::PageDown, || self.switch_stackframe(p, false)))
.chain(|i: Input| match self.available_display_mode() {
DisplayMode::Assembly | DisplayMode::SideBySide => {
let ret = self.asm_view.event(i, p);
if let Some(src_pos) = self
.asm_view
.pager
.current_line()
.and_then(|line| line.src_position.clone())
{
self.src_state = SrcContentState::NotYetLoaded(src_pos.file);
self.try_load_active_content(p);
let _ = self.src_view.go_to_line(src_pos.line);
}
ret
}
DisplayMode::Source => self.src_view.event(i, p),
DisplayMode::Message(_) => Some(i),
})
.finish()
}
fn as_widget<'e>(&'e self) -> Box<dyn Widget + 'e> {
let mode = self.available_display_mode();
let mut r = VLayout::new();
if let DisplayMode::Assembly | DisplayMode::Source | DisplayMode::SideBySide = mode {
r = r.widget(&self.stack_info)
}
r = match mode {
DisplayMode::Assembly => r.widget(self.asm_view.pager.as_widget()),
DisplayMode::SideBySide => r.widget(
HLayout::new()
.separator(GraphemeCluster::try_from('|').unwrap())
.widget(self.asm_view.pager.as_widget())
.widget(self.src_view.pager.as_widget()),
),
DisplayMode::Source => r.widget(self.src_view.pager.as_widget()),
DisplayMode::Message(m) => r.widget(m.centered().with_demand(|d| Demand2D {
width: ColDemand::at_least(d.width.min),
height: RowDemand::at_least(d.height.min),
})),
};
Box::new(r)
}
}