cortex-m assembler/disassembler into mcu - Page 1
Forum section: electronics / general

author: martinribelotta
author_rank: Regular Contributor
timestamp: July 13, 2020, 02:04:17 pm
content: Hi All, I'm working on a version of memorableDEBUG.COMfrom MSDOS but targetting ARMv7M/v6M. The interface does not seek to be the same as debug.com but to be in the same spirit.https://github.com/martinribelotta/cmx-debugFor now, this only have a hex dump and disassembler but I'm happy with result to be a weekend game, and it is clearly a work in progress project.I was test it in my bluepill board but the code is really generic... for the IO console I use semihosting facility from newlib but is retargetable to any interface like serial or whatever...The project is in GPLv3 licence because use binutils code (specifically a modified rutines from libopcodes)A little screenshoot:Thanks for reviewing and I hope someone will find it useful.

author: westfw
author_rank: Super Contributor
timestamp: July 14, 2020, 11:04:15 am
content: Nice. I wish you had posted this a couple of weeks back, when I started writing a disassembler from scratch (a quick look around found several that looked designed for big computers), and I didn’t even suspect that gdb’s would be useful.I had similar goals in mind, too. “Something like “debug” or “ddt””How much space is is taking?

author: martinribelotta
author_rank: Regular Contributor
timestamp: July 14, 2020, 04:31:46 pm
content: QuoteMayve you take ideas and some code from my project... Just remember that this code is tied to GPLv3 because it came (modified) from binutilsQuoteFrom my last compilation:Code:Memory region Used Size Region Size %age UsedRAM: 428 B 2 KB 20.90%ROM: 27012 B 64 KB 41.22%But this is mostly for newlib stdio libraries (I use newlib nano because no need floating point representation)A more presice information is reveal usingnm:Code:arm-none-eabi-nm -S -t dec --size-sort build/disassembler.o00000000 00000004 b ifthen_next_state00000000 00000016 T abort00000000 00000020 t print_addr00000000 00000068 r arm_conditional00000000 00000132 t data_barrier_option00000000 00000144 T do_disassemble00000000 00000152 t psr_name00000000 00000154 t arm_decode_bitfield00000000 00000408 t banked_regname00000000 00000432 r regnames00000000 00000672 r thumb_opcodes00000000 00001228 t print_insn_thumb1600000000 00002232 r thumb32_opcodes00000000 00002684 t print_insn_thumb32This is with-Ogoptimization... but no change significativelly with-Os:Code:arm-none-eabi-nm -S -t dec --size-sort build/disassembler.o00000000 00000004 b ifthen_next_state00000000 00000012 T abort00000000 00000024 t print_addr00000000 00000060 r CSWTCH.11800000000 00000068 r arm_conditional00000000 00000084 r CSWTCH.11900000000 00000432 r regnames00000000 00000448 r CSWTCH.11700000000 00000672 r thumb_opcodes00000000 00002232 r thumb32_opcodes00000000 00003348 T do_disassemble(this only merge some functions in one bigdo_disassemblesaving some code for enter/exit.In my first approach (one function for instruction encoding type) the memory usage was more than this approach (one big function in printf style with custom interpreter)

author: amyk
author_rank: Super Contributor
timestamp: July 15, 2020, 12:12:08 am
content: The technical term for this type of firmware is "ROM monitor".

author: brucehoult
author_rank: Super Contributor
timestamp: July 15, 2020, 12:30:12 am
content: Nice. If anyone wants to do the work, it might be worth making an ARM version based on the style and code used inhttps://github.com/riscv/riscv-opcodes, which is Berkeley license. This is used for the disassembler in the standard simulator (spike aka riscv-isa-sim) and many other things.

author: westfw
author_rank: Super Contributor
timestamp: July 15, 2020, 03:06:08 am
content: QuoteNoted. May be worth it anyway.Interestingly, a quick glance indicates that I have a very similar data structure for the decoding, except that I have code between multiple branches of the tree, rather than one huge table. I can't quite tell whether that'll be a net win; the 16bit thumb instructions were pretty easy, but the 32bit decode is painful :-(

author: brucehoult
author_rank: Super Contributor
timestamp: July 15, 2020, 03:28:56 am
content: Quote from: westfw on July 15, 2020, 03:06:08 amYeah, A32 and T16 encoding are reasonably pleasant, but T32 is just horrid. Weirdly, A64 isn't any better, despite the opportunity for a fresh start.The RISC-V code I've written or maintained doesn't use any fancy data structures for the instruction decode. Each instruction or instruction format  has a binary/hex "mask" and "match" pair. All the possibilities are tested sequentiallyif ((opcode & mask) == match) ...and then the variable fields for that instruction/format extracted. That's plenty fast enough for a disassembler (especially an interactive one). If more speed is needed then a cache (e.g. hash table) is kept of the N most recently seen raw instructions and their mapping to the appropriate mask/match pair or fully-decoded instruction struct or whatever. The number of completely distinct instruction opcodes in a program is relatively small, and certainly that's the case for a loop or function. it's a bit arbitrary where you draw the line e.g. do you decode add/sub/and/or/xor individually, or just do "ALU-OP" and treat the operation as a field?

author: Nominal Animal
author_rank: Super Contributor
timestamp: July 15, 2020, 06:04:26 am
content: I got a flashback to the Commodore 128monitor, the built-in system monitor utility I used to learn 6502 assembly. Used it almost as much as the built-in sprite editor (sprdef).Quote from: brucehoult on July 15, 2020, 03:28:56 amAre the mask bits continuous? Could you sort the mask,match tuples according to minimum or maximum possible opcode, so you could skip large swathes of opcodes? Or is the total number small enough to not bother? Sorry for regressing to a 4-year old, but this is kinda interesting.

author: brucehoult
author_rank: Super Contributor
timestamp: July 15, 2020, 08:01:09 am
content: Quote from: Nominal Animal on July 15, 2020, 06:04:26 amNo. It covers the whole 32 bit instruction, masking out all the fields containing register numbers, literals that parameterize an instruction (and the high 16 bits for 16 bit instructions), and keeping the fixed bits that make that instruction .. that instruction.You could sort by "mask". You could sort by "match". But I can't see any way to sort by a combination of them.RV64IMAFDC ends up with I guess a bit over 100 instructions in the list. That doesn't take long to do a linear search on.Many programs move a mask/match pair that hits to the front of the list. That means that most of the time you get a hit in the first 10 or so. Just a few instructions such as add immediate, load register, store register, beq, bne, jal, ret pretty quickly make up well over 90% of all opcodes.That's about as good as you'd get by sorting and binary searching anyway, even if it was possible.

author: Nominal Animal
author_rank: Super Contributor
timestamp: July 15, 2020, 09:28:37 am
content: Quote from: brucehoult on July 15, 2020, 08:01:09 amNo, that's definitely not too large to just scan through in linear fashion.Even if you stick the entire table in ROM/flash, you can reorder it with most commonly used instructions first. Personally, I might put the mask and match in one array, and the per-instruction data in a separate array, for optimal locality during the search, but that's about it.Even on x86-64, arrays that fit in a single (or a couple) cache line(s) are faster to scan in linear fashion than any alternatives. (I'vementionedI've done some research on how to repack large arrays so that binary search has better locality of access; I call them "chunked binary search trees".)

author: westfw
author_rank: Super Contributor
timestamp: July 15, 2020, 10:13:11 am
content: QuoteIt's a disassembler, operating at human-reading-text timescales. Doing 100-odd mask/compare/loop cycles is not a problem. Call it no more than 5000 instructions - even at 16MHz execution rates that's less than a millisecond per instruction.I was happy that I got my ARM disassembly code to automatically detect literal pools and NOT try to decode them. It was easier than I expected, too.

author: brucehoult
author_rank: Super Contributor
timestamp: July 15, 2020, 01:43:31 pm
content: Quote from: westfw on July 15, 2020, 10:13:11 amAs I said, spike is the reference RISC-V simulator, used for example by hardware designers to to run in lock-step with their FPGA or verilator runs to check them. It is written to be easy to understand and easy to extend, and to be "obviously correct". It uses this technique to match and decode instructions and runs at over 100 MIPS on a current PC. That's no more than 40 to 50 clock cycles on average to not only match the opcode with a linear search if necessary, but to extract the various fields and simulate it.

author: martinribelotta
author_rank: Regular Contributor
timestamp: July 15, 2020, 05:25:04 pm
content: QuoteWhat is your technic? I track instructions with memory access and mark these memori regions as LD/ST acceced... next, in disassembler, this is treat as literals.https://github.com/martinribelotta/cmx-debug/commit/79ba166322a9056d21f8412ee365af887abb8115This is only a concept implementation... I'm sure not catch all corner cases and not catch any non word access (LD*[B|H]) properly...

author: martinribelotta
author_rank: Regular Contributor
timestamp: July 15, 2020, 05:37:33 pm
content: QuoteThis is my first approach (multiple decode in tree with little tables and mask) but I spent two hours to get a simple branch and ld/st decoder... Then I thought someone could have done better and finally I searched several source codes (openocd, binutils, llvm)... The most simplest code to integrate was libopcodes from binutils...Hey, I admit it, I'm lazy ...Maybe, the tables in tree may be better optimized for code size and speed than the printf-like interpreter. Ideally, the best form to do the disassembling is emulating the combinational logic of decode circuit (maybe with some FSM emulating the control unit) but this approach is really hard and need many hours/man of reverse engineering from instruction format to <possible> decode implementation... This technique is not only useful for disassembly but also for emulation ... (clearly outside my current scope)