ARM/THUMB instructions that change execution path?

Has anybody come across a list of ARM & THUMB instructions that cause deviation from the linear instruction stream?

I've been trying to figure out gdb-stub single stepping using software interrupts, and in single stepping you need to find

the next instruction(s) where the next breakpoint instruction needs to be set.

There are three cases:

1) current instruction doesn't change the execution path. Next instruction is the next word.

2) current instruction is a jump. The operand defines the next instruction address

3) current instruction is conditional branch. One possible next instruction is the next word, the other possible

instruction address is defined by the operand. (That includes conditional add with PC as the target, and the like).

To implement single stepping, I need to tell those cases apart and figure out how to find out the possible branching address.

I could go through manuals of numerous processors instruction by instruction and maybe I'd be done within the next couple of years,

or I could find a list of instructions to check, or a paper that explains how to "decode" the instructions in a useful way.

Also, there doesn't seem to be lots of sources of ARM gdb servers or stubs around that use software breakpoints.

Parents
  • Well, the table method doesn't work, I saw that when I got the spreadsheet about the instructions done.

    When you look at the sheet, you can see that with all the instructions only bits 27, 26 and 25 are significant (= not belonging to an operand or modifier of some instruction). When you take a group of instructions that have bits 27, 26 and 25 all zeros, the only significant bit is bit 4. With instruction groups that have bits 27 - 25 and 4 zero, the new significant bits are 24, 23 and 21. If bit 4 is one, the only new significant bit is bit 7 and so on.

    Makes one h*** of a decoding logic.

    The spreadsheet is pretty nice tool for something like this: you can sort the lines (instructions) by different combinations of columns to find out which decoding steps work.

    I got the arithmetic & logic instructions nicely organized by selecting the lines and organizing them as bit 25 as primary, bit 4 as secondary and bit 21 as tertiary sorting "key". The instructions become sorted by addressing modes.

    First all register operation, then register shifted register and last the immediates. You can't apply all the keys at the same time, unfortunately, but one by one.

    This is what it looks like:

    cccc0000000SnnnnddddxxxxxTT0mmmm  AND{S}<c> <Rd>,<Rn>,<Rm>{,<sift>} A1 A8.8.14
    cccc0000010SnnnnddddxxxxxTT0mmmm  SUB{S}<c> <Rd>,<Rn>,<Rm>{,<sift>} A1 A8.8.223
    cccc0000100SnnnnddddxxxxxTT0mmmm  ADD{S}<c> <Rd>,<Rn>,<Rm>{,<sift>} A1 A8.8.7
    cccc0000110SnnnnddddxxxxxTT0mmmm  SBC{S}<c> <Rd>,<Rn>,<Rm>{,<sift>} A1 A8.8.162
    cccc0001100SnnnnddddxxxxxTT0mmmm  ORR{S}<c> <Rd>,<Rn>,<Rm>{,<sift>} A1 A8.8.123

    Sorting with bits 24 - 21 in that order gives:

    cccc0000000SnnnnddddxxxxxTT0mmmm  AND{S}<c> <Rd>,<Rn>,<Rm>{,<sift>} A1 A8.8.14
    cccc0000000Snnnnddddssss0TT1mmmm  AND{S}<c> <Rd>,<Rn>,<Rm>,<type>,<Rs> A1 A8.8.15
    cccc0010000Snnnnddddxxxxxxxxxxxx  AND{S}<c> <Rd>,<Rn>,#<const> A1 A8.8.13
    cccc0000001SnnnnddddxxxxxTT0mmmm  EOR{S}<c> <Rd>,<Rn>,<Rm>{,<sift>} A1 A8.8.47
    cccc0000001Snnnnddddssss0TT1mmmm  EOR{S}<c> <Rd>,<Rn>,<Rm>,<type>,<Rs> A1 A8.8.48
    cccc0010001Snnnnddddxxxxxxxxxxxx  EOR{S}<c> <Rd>,<Rn>,#<const> A1 A8.8.46
    cccc0000010SnnnnddddxxxxxTT0mmmm  SUB{S}<c> <Rd>,<Rn>,<Rm>{,<sift>} A1 A8.8.223
    cccc0000010Snnnnddddssss0TT1mmmm  SUB{S}<c> <Rd>,<Rn>,<Rm>,<type>,<Rs> A1 A8.8.224
    cccc0010010Snnnnddddxxxxxxxxxxxx  SUB{S}<c> <Rd>,<Rn>,#<const> A1 A8.8.222
    cccc0000011SnnnnddddxxxxxTT0mmmm  RSB{S}<c> <Rd>,<Rn>,<Rm>{,<sift>} A1 A8.8.153
Reply
  • Well, the table method doesn't work, I saw that when I got the spreadsheet about the instructions done.

    When you look at the sheet, you can see that with all the instructions only bits 27, 26 and 25 are significant (= not belonging to an operand or modifier of some instruction). When you take a group of instructions that have bits 27, 26 and 25 all zeros, the only significant bit is bit 4. With instruction groups that have bits 27 - 25 and 4 zero, the new significant bits are 24, 23 and 21. If bit 4 is one, the only new significant bit is bit 7 and so on.

    Makes one h*** of a decoding logic.

    The spreadsheet is pretty nice tool for something like this: you can sort the lines (instructions) by different combinations of columns to find out which decoding steps work.

    I got the arithmetic & logic instructions nicely organized by selecting the lines and organizing them as bit 25 as primary, bit 4 as secondary and bit 21 as tertiary sorting "key". The instructions become sorted by addressing modes.

    First all register operation, then register shifted register and last the immediates. You can't apply all the keys at the same time, unfortunately, but one by one.

    This is what it looks like:

    cccc0000000SnnnnddddxxxxxTT0mmmm  AND{S}<c> <Rd>,<Rn>,<Rm>{,<sift>} A1 A8.8.14
    cccc0000010SnnnnddddxxxxxTT0mmmm  SUB{S}<c> <Rd>,<Rn>,<Rm>{,<sift>} A1 A8.8.223
    cccc0000100SnnnnddddxxxxxTT0mmmm  ADD{S}<c> <Rd>,<Rn>,<Rm>{,<sift>} A1 A8.8.7
    cccc0000110SnnnnddddxxxxxTT0mmmm  SBC{S}<c> <Rd>,<Rn>,<Rm>{,<sift>} A1 A8.8.162
    cccc0001100SnnnnddddxxxxxTT0mmmm  ORR{S}<c> <Rd>,<Rn>,<Rm>{,<sift>} A1 A8.8.123

    Sorting with bits 24 - 21 in that order gives:

    cccc0000000SnnnnddddxxxxxTT0mmmm  AND{S}<c> <Rd>,<Rn>,<Rm>{,<sift>} A1 A8.8.14
    cccc0000000Snnnnddddssss0TT1mmmm  AND{S}<c> <Rd>,<Rn>,<Rm>,<type>,<Rs> A1 A8.8.15
    cccc0010000Snnnnddddxxxxxxxxxxxx  AND{S}<c> <Rd>,<Rn>,#<const> A1 A8.8.13
    cccc0000001SnnnnddddxxxxxTT0mmmm  EOR{S}<c> <Rd>,<Rn>,<Rm>{,<sift>} A1 A8.8.47
    cccc0000001Snnnnddddssss0TT1mmmm  EOR{S}<c> <Rd>,<Rn>,<Rm>,<type>,<Rs> A1 A8.8.48
    cccc0010001Snnnnddddxxxxxxxxxxxx  EOR{S}<c> <Rd>,<Rn>,#<const> A1 A8.8.46
    cccc0000010SnnnnddddxxxxxTT0mmmm  SUB{S}<c> <Rd>,<Rn>,<Rm>{,<sift>} A1 A8.8.223
    cccc0000010Snnnnddddssss0TT1mmmm  SUB{S}<c> <Rd>,<Rn>,<Rm>,<type>,<Rs> A1 A8.8.224
    cccc0010010Snnnnddddxxxxxxxxxxxx  SUB{S}<c> <Rd>,<Rn>,#<const> A1 A8.8.222
    cccc0000011SnnnnddddxxxxxTT0mmmm  RSB{S}<c> <Rd>,<Rn>,<Rm>{,<sift>} A1 A8.8.153
Children
  • I still think it's possible to use the table, especially, if you're only decoding 32-bit instructions.

    Let's make a few rules:

    1: If a bit is known to be either 1 or 0, set the corresponding bit in mask to 1, otherwise set it to 0.

    2: If a bit is known, set keep the bit in data, otherwise set the corresponding bit in data to 0.

    That is ...

    Instr: cccc0000010Snnnnddddssss0TT1mmmm

    mask : 00001111111000000000000010010000

    data : 00000000010000000000000000010000

    Using this rule, we can convert the above table using the attached perl-script.

    We now get the following (slightly decorated):

    static const InstrTab sInstructionTable[] = {

        0x0fe00010, 0x00000000, "and", &and_shift,  /* AND{S}<c> <Rd>,<Rn>,<Rm>{,<shift>} A1 A8.8.14 */

        0x0fe00090, 0x00000010, "and", &and_reg, /* AND{S}<c> <Rd>,<Rn>,<Rm>,<type>,<Rs> A1 A8.8.15 */

        0x0fe00000, 0x02000000, "and", &and_imm, /* AND{S}<c> <Rd>,<Rn>,#<const> A1 A8.8.13 */

        0x0fe00010, 0x00200000, "eor", &eor_shift, /* EOR{S}<c> <Rd>,<Rn>,<Rm>{,<shift>} A1 A8.8.47 */

        0x0fe00090, 0x00200010, "eor", &eor_reg, /* EOR{S}<c> <Rd>,<Rn>,<Rm>,<type>,<Rs> A1 A8.8.48 */

        0x0fe00000, 0x02200000, "eor", &eor_imm, /* EOR{S}<c> <Rd>,<Rn>,#<const> A1 A8.8.46 */

        0x0fe00010, 0x00400000, "sub", &sub_shift, /* SUB{S}<c> <Rd>,<Rn>,<Rm>{,<shift>} A1 A8.8.223 */

        0x0fe00090, 0x00400010, "sub", &sub_reg, /* SUB{S}<c> <Rd>,<Rn>,<Rm>,<type>,<Rs> A1 A8.8.224 */

        0x0fe00000, 0x02400000, "sub", &sub_imm, /* SUB{S}<c> <Rd>,<Rn>,#<const> A1 A8.8.222 */

        0x0fe00010, 0x00600000, "rsb", &rsb_shift, /* RSB{S}<c> <Rd>,<Rn>,<Rm>{,<shift>} A1 A8.8.153 */

        0x00000000, 0x00000000, "", &done, /* end of list */

    };

    Now, the problem with the table above, is that the order is incorrect. This can be fixed easily:

    static const InstrTab sInstructionTable[] = {

        0x0fe00090, 0x00000010, "and", &and_reg, /* AND{S}<c> <Rd>,<Rn>,<Rm>,<type>,<Rs> A1 A8.8.15 */

        0x0fe00010, 0x00000000, "and", &and_shift,  /* AND{S}<c> <Rd>,<Rn>,<Rm>{,<shift>} A1 A8.8.14 */

        0x0fe00000, 0x02000000, "and", &and_imm, /* AND{S}<c> <Rd>,<Rn>,#<const> A1 A8.8.13 */

        0x0fe00090, 0x00200010, "eor", &eor_reg, /* EOR{S}<c> <Rd>,<Rn>,<Rm>,<type>,<Rs> A1 A8.8.48 */

        0x0fe00010, 0x00200000, "eor", &eor_shift, /* EOR{S}<c> <Rd>,<Rn>,<Rm>{,<shift>} A1 A8.8.47 */

        0x0fe00000, 0x02200000, "eor", &eor_imm, /* EOR{S}<c> <Rd>,<Rn>,#<const> A1 A8.8.46 */

        0x0fe00090, 0x00400010, "sub", &sub_reg, /* SUB{S}<c> <Rd>,<Rn>,<Rm>,<type>,<Rs> A1 A8.8.224 */

        0x0fe00010, 0x00400000, "sub", &sub_shift, /* SUB{S}<c> <Rd>,<Rn>,<Rm>{,<shift>} A1 A8.8.223 */

        0x0fe00000, 0x02400000, "sub", &sub_imm, /* SUB{S}<c> <Rd>,<Rn>,#<const> A1 A8.8.222 */

        0x0fe00010, 0x00600000, "rsb", &rsb_shift, /* RSB{S}<c> <Rd>,<Rn>,<Rm>{,<shift>} A1 A8.8.153 */

        0x00000000, 0x00000000, "", &done, /* end of list */

    };

    (First two AND were swapped, first two EOR were swapped and first two SUB were swapped).

    -Of course, the Perl-script could be enhanced to do this automatically, generating extra masks and data in those cases where that is needed.

    So the following should work:

        InstrTab *tab;
        uint32_t instr;
    
        instr = *pc;
        tab = &sInstructionTable;
        while((instr & tab->mask) != tab->data)
        {
            tab++;
        }
        tab->handle(instr,tab);
    
    
    
    

    If writing the code in assembly language, I believe it would be a good idea to re-arrange the contents of the table, so that the instruction name comes first, then the mask, the data and finally the handler.

    find_instr:

        ldmia   r4!,{r1-r3,r12}

        and     r2,r2,r0

        cmp     r3,r2

        bne     find_instr

        /* here r0 contains the instruction opcode, r1 contains the name and r12 contains the address of the handler */

        pop     { ... }          /* restore any saved registers */

        bx      r12              /* jump directly to the handler */


  • Hmm, I guess I've been working too hard with the project - I didn't come to think adding the handler in the table.

    (I don't like to use function pointers if not necessary - the execution path gets fuzzy and some debuggers can't handle them, but using an enum...) I had a different idea about what the table method meas, but I don't remember what I thought any more. What I remember is that I didn't think of comparing an instruction to all instructions of the instruction set.

    If applied to the whole instruction set, it may be a bit heavy for single stepping - on the average it means going through half of the table for each instruction each time.

    Then again writing the logic in C code takes a lot of time, and the code in not something you'd show to small children...

    (I made the pseudocode whith which I'm still not completely happy, but the C-coding of it is not started yet.)

    Maybe I should use the table approach now after all, and go for the C code logic later (if I still feel like it).

    I really have to rethink this.

    Especially now that I have the instruction set in a spreadsheet from which the instruction bit patterns are easily edited (bits into bytes, byte order, ...) and they can be printed out as a text file for simple editing (if any needed) into a table initializer data form.  Also the mask and data can be easily generated with spreadsheet formuli.

    The table is also quite compact form - the instruction set itself probably fits in 1 kB, and the actual handlers need to be written anyway.

    Too bad there is only 'like'-button. There should also be 'Halleluyah'-button.

  • When I wrote my 68xxx debugger, the table was fine (though these were only 16-bit words).

    -But ARM's instruction set is not too complicated either. I have not had a look at Cortex-A yet, but the time will come.

    In case the table gets too large, you have an extra approach: To split the words into two 16-bit words, so you find the "main part", which leads to a "sub-tree".

    Remember: The execution unit in the processor does the job very, very quickly, so I am convinced that ARM designed the instruction set, so it should be easy to dispatch (even by using code).

    Yes, the hard part is to find out how.

    The good thing about using pointers, is that you can make your lookup-routine in assembly language, and it can jump directly to your C routine.

    You can then call it as a C-function, because it uses "goto-style", thus it'll be completely transparent and your C-code will behave like a normal subroutine-call; just very quickly.

    The above look-up example can be unrolled easily; this will save a few clock cycles on each iteration:

    find_instr:

        ldmia   r4!,{r1-r3,r12}

        and     r2,r2,r0

        cmp     r3,r2

        .rept   7

        ldmiane r4!,{r1-r3,r12}

        andne   r2,r2,r0

        cmpne   r3,r2

        .endr

        bne     find_instr

        /* here r0 contains the instruction opcode, r1 contains the name and r12 contains the address of the handler */

        pop     { ... }          /* restore any saved registers */

        bx      r12              /* jump directly to the handler */


    -Change the '.rept' count as you like... make it 15 or 31, adjust it to suit your needs. Perhaps a large number may start to cause longer execution time, but it's a question of balance.

    If you're lucky, you can place instructions that are used often in the beginning of the table (I did that with my debugger, and it started to become quite quick at disassembling).

    This kind of code is something I really like. The table-lookup, masks and AND stuff - it brings out good memories too.

    -But of course, sometimes it might be easier or shorter or quicker to write a switch-statement and use enumerations for handling each instruction type.

    Some instruction types could be handled by the same handler; eg. AND/ORR/EOR and ADD/SUB.

    In many cases, it's useful to think of instructions as being in "instruction groups". Eg. LDR/STR is a good example, AND/ORR/EOR, ASL/ASR/LSL/LSR/ROR, etc.

  • If you're lucky, you can place instructions that are used often in the beginning of the table

    You read my mind.

    But ARM's instruction set is not too complicated either.

    Assembly is not, but the encoding is.

    cccc0000000Sdddd(0)(0)(0)(0)mmmm1001nnnn  MUL{S}<c> <Rd>,<Rn>,<Rm> A1A8.8.114
    cccc0000000Snnnnddddssss0TT1mmmm  AND{S}<c> <Rd>,<Rn>,<Rm>,<type>,<Rs> A1A8.8.15
    cccc0000000SnnnnddddxxxxxTT0mmmm  AND{S}<c> <Rd>,<Rn>,<Rm>{,<sift>} A1A8.8.14
    cccc0000001Sddddaaaammmm1001nnnn  MLA{S}<c> <Rd>,<Rn>,<Rm>,<Ra> A1A8.8.100
    cccc0000001Snnnnddddssss0TT1mmmm  EOR{S}<c> <Rd>,<Rn>,<Rm>,<type>,<Rs> A1A8.8.48
    cccc0000001SnnnnddddxxxxxTT0mmmm  EOR{S}<c> <Rd>,<Rn>,<Rm>{,<sift>} A1A8.8.47
    cccc00000100hhhhllllmmmm1001nnnn  UMAAL<c> <RdLo>,<RdHi>,<Rn>,<Rm> A1A8.8.255
    cccc0000010S1101ddddxxxxxTT0mmmm  SUB{S}<c> <Rd>,SP,<Rm>{,<sift>} A1A8.8.226
    cccc0000010Snnnnddddssss0TT1mmmm  SUB{S}<c> <Rd>,<Rn>,<Rm>,<type>,<Rs> A1A8.8.224

    and:

    cccc0001100Snnnnddddssss0TT1mmmm  ORR{S}<c> <Rd>,<Rn>,<Rm>,<type>,<Rs> A1A8.8.124
    cccc0001100SnnnnddddxxxxxTT0mmmm  ORR{S}<c> <Rd>,<Rn>,<Rm>{,<sift>} A1A8.8.123
    cccc00011010nnnndddd(1)(1)(1)(1)1001tttt  STREXD<c> <Rd>,<Rt>,<Rt2>,[<Rn>] A1A8.8.214
    cccc00011011nnnntttt(1)(1)(1)(1)1001(1)(1)(1)(1)  LDREXD<c> <Rt>,<Rt2>,[<Rn>] A1A8.8.77
    cccc0001101S(0)(0)(0)(0)dddd00000000mmmm  MOV{S}<c> <Rd>,<Rm> A1A8.8.104
    cccc0001101S(0)(0)(0)(0)dddd00000110mmmm  RRX{S}<c> <Rd>,<Rm> A1A8.8.151
    cccc0001101S(0)(0)(0)(0)ddddmmmm0001nnnn  LSL{S}<c> <Rd>,<Rn>,<Rm> A1A8.8.95
    cccc0001101S(0)(0)(0)(0)ddddmmmm0011nnnn  LSR{S}<c> <Rd>,<Rn>,<Rm> A1A8.8.97
    cccc0001101S(0)(0)(0)(0)ddddmmmm0101nnnn  ASR{S}<c> <Rd>,<Rn>,<Rm> A1A8.8.17

    Oh, and for small assembly routines I've been using inline asm, like

    void rpi2_trap_handler()

    {

        // IRQs need to be enabled for serial I/O

        asm volatile (

                "push {r0}\n\t"

                "mrs r0, cpsr\n\t"

                "bic r0, #128 @ enable irqs\n\t"

                "msr cpsr, r0\n\t"

                "pop {r0}\n\t"

        );

        gdb_trap_handler();

    }

    :