From 03195921b88e36bfd745e6114229acfbd012c622 Mon Sep 17 00:00:00 2001 From: LongHairedHacker Date: Fri, 23 Aug 2019 18:50:19 +0200 Subject: [PATCH] I2C works --- .gdbdash | 388 ++++++++++++++++++++++++++++++++++++---------------- Cargo.toml | 4 +- run.sh | 2 +- src/main.rs | 36 ++++- 4 files changed, 299 insertions(+), 131 deletions(-) diff --git a/.gdbdash b/.gdbdash index fc838dc..c13778a 100644 --- a/.gdbdash +++ b/.gdbdash @@ -5,11 +5,9 @@ python # https://github.com/cyrus-and/gdb-dashboard import ast -import fcntl import os import re import struct -import termios import traceback import math @@ -34,8 +32,23 @@ The list of all the available styles can be obtained with (from GDB itself): python from pygments.styles import get_all_styles as styles python for s in styles(): print(s) """, - 'default': 'vim', - 'type': str + 'default': 'vim' + }, + # values formatting + 'compact_values': { + 'doc': 'Display complex objects in a single line.', + 'default': False, + 'type': bool + }, + 'max_value_length': { + 'doc': 'Maximum length for displayed values.', + 'default': 0, + 'type': int + }, + 'dereference': { + 'doc': 'Annotate pointers with the pointed value.', + 'default': True, + 'type': bool }, # prompt 'prompt': { @@ -183,39 +196,59 @@ def to_string(value): value_string = str(value) except UnicodeEncodeError: value_string = unicode(value).encode('utf8') + except gdb.error as e: + value_string = ansi(e, R.style_error) return value_string def format_address(address): pointer_size = gdb.parse_and_eval('$pc').type.sizeof return ('0x{{:0{}x}}').format(pointer_size * 2).format(address) -def format_value(value): +def format_value(value, compact=None): # format references as referenced values # (TYPE_CODE_RVALUE_REF is not supported by old GDB) if value.type.code in (getattr(gdb, 'TYPE_CODE_REF', None), getattr(gdb, 'TYPE_CODE_RVALUE_REF', None)): - try: - return to_string(value.referenced_value()) - except gdb.MemoryError: - return to_string(value) - else: - try: - return to_string(value) - except gdb.MemoryError as e: - return ansi(e, R.style_error) + value = value.referenced_value() + # format the value + out = to_string(value) + # dereference up to the actual value if requested + if R.dereference and value.type.code == gdb.TYPE_CODE_PTR: + while value.type.code == gdb.TYPE_CODE_PTR: + try: + value = value.dereference() + except gdb.error as e: + break + else: + formatted = to_string(value) + out += '{} {}'.format(ansi(':', R.style_low), formatted) + # compact the value + if compact is not None and compact or R.compact_values: + out = re.sub(r'$\s*', '', out, flags=re.MULTILINE) + # truncate the value + if R.max_value_length > 0 and len(out) > R.max_value_length: + out = out[0:R.max_value_length] + ansi('[...]', R.style_error) + return out class Beautifier(): - def __init__(self, filename, tab_size=4): + def __init__(self, hint, tab_size=4): self.tab_spaces = ' ' * tab_size self.active = False if not R.ansi: return # attempt to set up Pygments try: - from pygments.lexers import get_lexer_for_filename + import pygments + from pygments.lexers import GasLexer, NasmLexer from pygments.formatters import Terminal256Formatter + if hint == 'att': + self.lexer = GasLexer() + elif hint == 'intel': + self.lexer = NasmLexer() + else: + from pygments.lexers import get_lexer_for_filename + self.lexer = get_lexer_for_filename(hint, stripnl=False) self.formatter = Terminal256Formatter(style=R.syntax_highlighting) - self.lexer = get_lexer_for_filename(filename, stripnl=False) self.active = True except ImportError: # Pygments not available @@ -383,8 +416,13 @@ class Dashboard(gdb.Command): # skip disabled modules if not instance: continue - # ask the module to generate the content - lines = instance.lines(width, style_changed) + try: + # ask the module to generate the content + lines = instance.lines(width, style_changed) + except Exception as e: + # allow to continue on exceptions in modules + stacktrace = traceback.format_exc().strip() + lines = [ansi(stacktrace, R.style_error)] # create the divider accordingly div = divider(width, instance.label(), True, lines) # write the data @@ -427,10 +465,22 @@ class Dashboard(gdb.Command): @staticmethod def get_term_width(fd=1): # defaults to the main terminal - # first 2 shorts (4 byte) of struct winsize - raw = fcntl.ioctl(fd, termios.TIOCGWINSZ, ' ' * 4) - height, width = struct.unpack('hh', raw) - return int(width) + if sys.platform == 'win32': + try: + import curses + # XXX always neglects the fd parameter + _, width = curses.initscr().getmaxyx() + curses.endwin() + return int(width) + except ImportError: + return 80 # hardcoded fallback value + else: + import termios + import fcntl + # first 2 shorts (4 byte) of struct winsize + raw = fcntl.ioctl(fd, termios.TIOCGWINSZ, ' ' * 4) + _, width = struct.unpack('hh', raw) + return int(width) @staticmethod def set_custom_prompt(dashboard): @@ -959,11 +1009,9 @@ instructions constituting the current statement are marked, if available.""" frame = gdb.selected_frame() # PC is here disassemble = frame.architecture().disassemble try: - # try to fetch the function boundaries using the disassemble command - output = run('disassemble').split('\n') - start = int(re.split('[ :]', output[1][3:], 1)[0], 16) - end = int(re.split('[ :]', output[-3][3:], 1)[0], 16) - asm = disassemble(start, end_pc=end) + # disassemble the current block + block = gdb.block_for_pc(frame.pc()) + asm = disassemble(block.start, end_pc=block.end - 1) # find the location of the PC pc_index = next(index for index, instr in enumerate(asm) if instr['addr'] == frame.pc()) @@ -974,7 +1022,7 @@ instructions constituting the current statement are marked, if available.""" # line_info is not None but line_info.last is None line_info = gdb.find_pc_line(frame.pc()) line_info = line_info if line_info.last else None - except (gdb.error, StopIteration): + except (gdb.error, RuntimeError, StopIteration): # if it is not possible (stripped binary or the PC is not present in # the output of `disassemble` as per issue #31) start from PC and # end after twice the context @@ -995,17 +1043,13 @@ instructions constituting the current statement are marked, if available.""" func_start = to_unsigned(value) except gdb.error: pass # e.g., @plt - # fetch the assembly flavor and the extension used by Pygments + # fetch the assembly flavor anduse it as hint for Pygments try: flavor = gdb.parameter('disassembly-flavor') except: - flavor = None # not always defined (see #36) - filename = { - 'att': '.s', - 'intel': '.asm' - }.get(flavor, '.s') + flavor = 'att' # not always defined (see #36) # prepare the highlighter - highlighter = Beautifier(filename) + highlighter = Beautifier(flavor) # compute the maximum offset size if func_start: max_offset = max(len(str(abs(asm[0]['addr'] - func_start))), @@ -1087,6 +1131,84 @@ instructions constituting the current statement are marked, if available.""" } } +class Variables(Dashboard.Module): + """Show arguments and locals of the selected frame.""" + + def label(self): + return 'Variables' + + def lines(self, term_width, style_changed): + return Variables.format_variables( + gdb.selected_frame(), + self.show_arguments, self.show_locals, self.compact) + + def attributes(self): + return { + 'arguments': { + 'doc': 'Frame arguments visibility flag.', + 'default': True, + 'name': 'show_arguments', + 'type': bool + }, + 'locals': { + 'doc': 'Frame locals visibility flag.', + 'default': True, + 'name': 'show_locals', + 'type': bool + }, + 'compact': { + 'doc': 'Single-line display flag.', + 'default': False, + 'type': bool + } + } + + @staticmethod + def format_variables(frame, show_arguments, show_locals, compact): + out = [] + # fetch frame arguments and locals + decorator = gdb.FrameDecorator.FrameDecorator(frame) + separator = ansi(', ', R.style_low) + if show_arguments: + def prefix(line): + return Stack.format_line('arg', line) + frame_args = decorator.frame_args() + args_lines = Variables.fetch(frame, frame_args, compact) + if args_lines: + if compact: + args_line = separator.join(args_lines) + single_line = prefix(args_line) + out.append(single_line) + else: + out.extend(map(prefix, args_lines)) + else: + out.append(ansi('(no arguments)', R.style_low)) + if show_locals: + def prefix(line): + return Stack.format_line('loc', line) + frame_locals = decorator.frame_locals() + locals_lines = Variables.fetch(frame, frame_locals, compact) + if locals_lines: + if compact: + locals_line = separator.join(locals_lines) + single_line = prefix(locals_line) + out.append(single_line) + else: + out.extend(map(prefix, locals_lines)) + else: + out.append(ansi('(no locals)', R.style_low)) + return out + + @staticmethod + def fetch(frame, data, compact): + lines = [] + for elem in data or []: + name = elem.sym + equal = ansi('=', R.style_low) + value = format_value(elem.sym.value(frame), compact) + lines.append('{} {} {}'.format(name, equal, value)) + return lines + class Stack(Dashboard.Module): """Show the current stack trace including the function name and the file location, if available. Optionally list the frame arguments and locals too.""" @@ -1119,40 +1241,9 @@ location, if available. Optionally list the frame arguments and locals too.""" info = Stack.get_pc_line(frame, style) frame_lines = [] frame_lines.append('[{}] {}'.format(frame_id, info)) - # fetch frame arguments and locals - decorator = gdb.FrameDecorator.FrameDecorator(frame) - separator = ansi(', ', R.style_low) - strip_newlines = re.compile(r'$\s*', re.MULTILINE) - if self.show_arguments: - def prefix(line): - return Stack.format_line('arg', line) - frame_args = decorator.frame_args() - args_lines = Stack.fetch_frame_info(frame, frame_args) - if args_lines: - if self.compact: - args_line = separator.join(args_lines) - args_line = strip_newlines.sub('', args_line) - single_line = prefix(args_line) - frame_lines.append(single_line) - else: - frame_lines.extend(map(prefix, args_lines)) - else: - frame_lines.append(ansi('(no arguments)', R.style_low)) - if self.show_locals: - def prefix(line): - return Stack.format_line('loc', line) - frame_locals = decorator.frame_locals() - locals_lines = Stack.fetch_frame_info(frame, frame_locals) - if locals_lines: - if self.compact: - locals_line = separator.join(locals_lines) - locals_line = strip_newlines.sub('', locals_line) - single_line = prefix(locals_line) - frame_lines.append(single_line) - else: - frame_lines.extend(map(prefix, locals_lines)) - else: - frame_lines.append(ansi('(no locals)', R.style_low)) + # add frame arguments and locals + frame_lines.extend(Variables.format_variables( + frame, self.show_arguments, self.show_locals, self.compact)) # add frame frames.append(frame_lines) # next @@ -1178,16 +1269,6 @@ location, if available. Optionally list the frame arguments and locals too.""" prefix = ansi(prefix, R.style_low) return '{} {}'.format(prefix, line) - @staticmethod - def fetch_frame_info(frame, data): - lines = [] - for elem in data or []: - name = elem.sym - equal = ansi('=', R.style_low) - value = format_value(elem.sym.value(frame)) - lines.append('{} {} {}'.format(name, equal, value)) - return lines - @staticmethod def get_pc_line(frame, style): frame_pc = ansi(format_address(frame.pc()), style) @@ -1220,13 +1301,13 @@ location, if available. Optionally list the frame arguments and locals too.""" return { 'limit': { 'doc': 'Maximum number of displayed frames (0 means no limit).', - 'default': 2, + 'default': 10, 'type': int, 'check': check_ge_zero }, 'arguments': { 'doc': 'Frame arguments visibility flag.', - 'default': True, + 'default': False, 'name': 'show_arguments', 'type': bool }, @@ -1275,6 +1356,61 @@ class History(Dashboard.Module): class Memory(Dashboard.Module): """Allow to inspect memory regions.""" + class Region(): + def __init__(self, address, length, module): + self.address = address + self.length = length + self.module = module + self.original = None + self.latest = None + + def format(self): + # fetch the memory content + try: + inferior = gdb.selected_inferior() + memory = inferior.read_memory(self.address, self.length) + # set the original memory snapshot if needed + if not self.original: + self.original = memory + except gdb.error: + msg = 'Cannot access {} bytes starting at {}' + msg = msg.format(self.length, format_address(self.address)) + return [ansi(msg, R.style_error)] + + # format the memory content + out = [] + for i in range(0, len(memory), self.module.row_length): + region = memory[i:i + self.module.row_length] + pad = self.module.row_length - len(region) + address = format_address(self.address + i) + # compute changes + hexa = [] + text = [] + for j in range(len(region)): + rel = i + j + byte = memory[rel] + hexa_byte = '{:02x}'.format(ord(byte)) + text_byte = Memory.format_byte(byte) + # differences against the latest have the highest priority + if self.latest and memory[rel] != self.latest[rel]: + hexa_byte = ansi(hexa_byte, R.style_selected_1) + text_byte = ansi(text_byte, R.style_selected_1) + # cumulative changes if enabled + elif (self.module.cumulative and + memory[rel] != self.original[rel]): + hexa_byte = ansi(hexa_byte, R.style_selected_2) + text_byte = ansi(text_byte, R.style_selected_2) + hexa.append(hexa_byte) + text.append(text_byte) + # output the formatted line + out.append('{} {}{} {}{}'.format( + ansi(address, R.style_low), + ' '.join(hexa), ansi(pad * ' --', R.style_low), + ''.join(text), ansi(pad * '.', R.style_low))) + # update the latest memory snapshot + self.latest = memory + return out + @staticmethod def format_byte(byte): # `type(byte) is bytes` in Python 3 @@ -1294,35 +1430,13 @@ class Memory(Dashboard.Module): self.row_length = 16 self.table = {} - def format_memory(self, start, memory): - out = [] - for i in range(0, len(memory), self.row_length): - region = memory[i:i + self.row_length] - pad = self.row_length - len(region) - address = format_address(start + i) - hexa = (' '.join('{:02x}'.format(ord(byte)) for byte in region)) - text = (''.join(Memory.format_byte(byte) for byte in region)) - out.append('{} {}{} {}{}'.format(ansi(address, R.style_low), - hexa, - ansi(pad * ' --', R.style_low), - ansi(text, R.style_high), - ansi(pad * '.', R.style_low))) - return out - def label(self): return 'Memory' def lines(self, term_width, style_changed): out = [] - inferior = gdb.selected_inferior() - for address, length in sorted(self.table.items()): - try: - memory = inferior.read_memory(address, length) - out.extend(self.format_memory(address, memory)) - except gdb.error: - msg = 'Cannot access {} bytes starting at {}' - msg = msg.format(length, format_address(address)) - out.append(ansi(msg, R.style_error)) + for address, region in sorted(self.table.items()): + out.extend(region.format()) out.append(divider(term_width)) # drop last divider if out: @@ -1337,7 +1451,7 @@ class Memory(Dashboard.Module): length = Memory.parse_as_address(length) else: length = self.row_length - self.table[address] = length + self.table[address] = Memory.Region(address, length, self) else: raise Exception('Specify an address') @@ -1372,6 +1486,15 @@ class Memory(Dashboard.Module): } } + def attributes(self): + return { + 'cumulative': { + 'doc': 'Highlight changes cumulatively, watch again to reset.', + 'default': False, + 'type': bool + } + } + class Registers(Dashboard.Module): """Show the CPU registers and their values.""" @@ -1385,11 +1508,18 @@ class Registers(Dashboard.Module): # skip if the current thread is not stopped if not gdb.selected_thread().is_stopped(): return [] + # obtain the registers to display + if style_changed: + self.table = {} + if self.register_list: + register_list = self.register_list.split() + else: + register_list = list(map(lambda line: line.split(None, 1)[0], + run('info registers').strip().split('\n'))) + # fetch registers status registers = [] - for reg_info in run('info registers').strip().split('\n'): - # fetch register and update the table - name = reg_info.split(None, 1)[0] + for name in register_list: # Exclude registers with a dot '.' or parse_and_eval() will fail if '.' in name: continue @@ -1404,15 +1534,10 @@ class Registers(Dashboard.Module): max_name = max(len(name) for name, _, _ in registers) max_value = max(len(value) for _, value, _ in registers) max_width = max_name + max_value + 2 - per_line = int((term_width + 1) / max_width) or 1 + per_line = min(int((term_width + 1) / max_width) or 1, len(registers)) # redistribute extra space among columns extra = int((term_width + 1 - max_width * per_line) / per_line) - if per_line == 1: - # center when there is only one column - max_name += int(extra / 2) - max_value += int(extra / 2) - else: - max_value += extra + max_name += int(extra / 2) # format registers info partial = [] for name, value, changed in registers: @@ -1440,6 +1565,12 @@ class Registers(Dashboard.Module): 'default': False, 'name': 'column_major', 'type': bool + }, + 'list': { + 'doc': """String of space-separated register names to display. +The empty list (default) causes to show all the available registers.""", + 'default': '', + 'name': 'register_list', } } @@ -1468,13 +1599,22 @@ class Threads(Dashboard.Module): restore_frame = gdb.selected_thread().is_stopped() if restore_frame: selected_frame = gdb.selected_frame() - for thread in gdb.Inferior.threads(gdb.selected_inferior()): + # fetch the thread list + threads = [] + for inferior in gdb.inferiors(): + if self.all_inferiors or inferior == gdb.selected_inferior(): + threads += gdb.Inferior.threads(inferior) + for thread in threads: # skip running threads if requested if self.skip_running and thread.is_running(): continue is_selected = (thread.ptid == selected_thread.ptid) style = R.style_selected_1 if is_selected else R.style_selected_2 - number = ansi(str(thread.num), style) + if self.all_inferiors: + number = '{}.{}'.format(thread.inferior.num, thread.num) + else: + number = str(thread.num) + number = ansi(number, style) tid = ansi(str(thread.ptid[1] or thread.ptid[2]), style) info = '[{}] id {}'.format(number, tid) if thread.name: @@ -1500,7 +1640,13 @@ class Threads(Dashboard.Module): 'default': False, 'name': 'skip_running', 'type': bool - } + }, + 'all-inferiors': { + 'doc': 'Show threads from all inferiors.', + 'default': False, + 'name': 'all_inferiors', + 'type': bool + }, } class Expressions(Dashboard.Module): @@ -1579,7 +1725,7 @@ set python print-stack full python Dashboard.start() # ------------------------------------------------------------------------------ -# Copyright (c) 2015-2017 Andrea Cardaci +# Copyright (c) 2015-2019 Andrea Cardaci # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal diff --git a/Cargo.toml b/Cargo.toml index a6a0c5d..93311ed 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,7 +2,7 @@ authors = ["sebastian"] edition = "2018" readme = "README.md" -name = "STM32F1Test" +name = "cheapsdo" version = "0.1.0" [dependencies] @@ -27,7 +27,7 @@ features = ["unproven"] # this lets you use `cargo fix`! [[bin]] -name = "STM32F1Test" +name = "cheapsdo" test = false bench = false diff --git a/run.sh b/run.sh index 3574304..94f190b 100755 --- a/run.sh +++ b/run.sh @@ -1,3 +1,3 @@ #!/bin/bash cargo build || exit -1 -arm-none-eabi-gdb target/thumbv7m-none-eabi/debug/STM32F1Test +arm-none-eabi-gdb target/thumbv7m-none-eabi/debug/cheapsdo diff --git a/src/main.rs b/src/main.rs index 44c3c72..187ef2b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,21 +1,16 @@ -//! Blinks an LED -//! -//! This assumes that a LED is connected to pc13 as is the case on the blue pill board. -//! -//! Note: Without additional hardware, PC13 should not be used to drive an LED, see page 5.1.2 of -//! the reference manaual for an explanation. This is not an issue on the blue pill. - #![deny(unsafe_code)] #![no_std] #![no_main] use panic_halt as _; +use cortex_m_semihosting::hprintln; // logs messages to the host; requires a debugger use nb::block; use stm32f1xx_hal::{ prelude::*, pac, + i2c, timer::Timer, }; use cortex_m_rt::entry; @@ -23,6 +18,7 @@ use embedded_hal::digital::v2::OutputPin; #[entry] fn main() -> ! { + // Get access to the core peripherals from the cortex-m crate let cp = cortex_m::Peripherals::take().unwrap(); // Get access to the device specific peripherals from the peripheral access crate @@ -32,6 +28,7 @@ fn main() -> ! { // HAL structs let mut flash = dp.FLASH.constrain(); let mut rcc = dp.RCC.constrain(); + let mut afio = dp.AFIO.constrain(&mut rcc.apb2); // Freeze the configuration of all the clocks in the system and store the frozen frequencies in // `clocks` @@ -40,6 +37,23 @@ fn main() -> ! { // Acquire the GPIOC peripheral let mut gpioc = dp.GPIOC.split(&mut rcc.apb2); + let mut gpiob = dp.GPIOB.split(&mut rcc.apb2); + let scl = gpiob.pb6.into_alternate_open_drain(&mut gpiob.crl); + let sda = gpiob.pb7.into_alternate_open_drain(&mut gpiob.crl); + + let mut i2c = i2c::BlockingI2c::i2c1(dp.I2C1, (scl, sda), + &mut afio.mapr, + i2c::Mode::Standard{frequency: 400_000}, + clocks, + &mut rcc.apb1, + 5, + 1, + 5, + 5); + + hprintln!("I2C setup").unwrap(); + + // Configure gpio C pin 13 as a push-pull output. The `crh` register is passed to the function // in order to configure the port. For pins 0-7, crl should be passed instead. let mut led = gpioc.pc13.into_push_pull_output(&mut gpioc.crh); @@ -50,6 +64,14 @@ fn main() -> ! { loop { block!(timer.wait()).unwrap(); led.set_high().unwrap(); + + let res = i2c.write(96, &[0x23, 0x82]); + if res.is_ok() { + hprintln!("write worked!").unwrap(); + } else { + hprintln!("write failed!").unwrap(); + } + block!(timer.wait()).unwrap(); led.set_low().unwrap(); }