如何生成Thumb指令的机器码?

How to generate the machine code of Thumb instructions?

我搜索了 Google 来生成 ARM 指令的机器代码,例如这个 Converting very simple ARM instructions to binary/hex

答案参考了ARM7TDMI-S数据Sheet (ARM DDI 0084D)。数据处理指令的图表就足够了。不幸的是,它适用于 ARM 指令,而不适用于 Thumb/Thumb-2 指令。

以B指令为例。 ARM 体系结构参考手册 - ARMv7-A 和 ARMv7-R 版 A8.8.18 节,编码 T4:

对于汇编代码:

B 0x50

如何将立即数0x50编码成4字节的机器码?或者,如果我想编写一个 C 函数,它接受 B 指令和作为输入,以及 return 编码的机器代码。我该如何实现这样的功能?

unsigned int gen_mach_code(int instruction, int relative_addr)
{
    /* the int instruction parameter is assumed to be B */
    /* encoding method is assumed to be T4 */
    unsigned int mach_code;
    /* construc the machine code of B<c>.W <label> */
    return mach_code;
}

我知道 ARM 上的立即数编码。这里 http://alisdair.mcdiarmid.org/arm-immediate-value-encoding/ 是一个很好的教程。

我只想知道imm10和imm11从哪里来,以及如何用它们构造完整的机器码。

首先,ARM7TDMI 不支持 thumb2 扩展,相反,它基本上定义了原始的 thumb 指令集。

为什么不试试呢?

.thumb
@.syntax unified

b 0x50

运行 这些命令

arm-whatever-whatever-as b.s -o b.o
arm-whatever-whatever-objdump -D b.o

得到这个输出

0:  e7fe        b.n 50 <*ABS*0x50>

所以这是一个 T2 编码,正如 ARMv4T、ARMv5T*、ARMv6*、ARMv7 支持的该指令的较新文档所示,ARM7TDMI 是一个 ARMv4t

所以我们看到 E7 匹配该指令定义的 11100 开头 所以 imm11 是 0x7FE。这基本上是对地址 0x000 的分支编码,因为它没有与任何东西链接。我怎么知道的?

.thumb
b skip
nop
nop
nop
nop
nop
skip:

00000000 <skip-0xc>:
   0:   e004        b.n c <skip>
   2:   46c0        nop         ; (mov r8, r8)
   4:   46c0        nop         ; (mov r8, r8)
   6:   46c0        nop         ; (mov r8, r8)
   8:   46c0        nop         ; (mov r8, r8)
   a:   46c0        nop         ; (mov r8, r8)

0xe004 以 11100 开头,因此这是一个分支编码 T2。 imm11 是一个 4

我们需要从 0 到达 0xC。应用偏移量时,PC 提前两条指令。文档说

Encoding T2 Even numbers in the range –2048 to 2046

PC, the program counter 
- When executing an ARM instruction, PC reads as the address of the current instruction plus 8. • When executing a
- Thumb instruction, PC reads as the address of the current instruction
plus 4.

所以一切都说得通。 0xC-0x4 = 8。我们只能做偶数,无论如何分支到指令的中间是没有意义的,所以除以 2 因为拇指指令是两个字节(偏移量在指令中而不是字节)。所以给出 4

0xE004

这是生成 t4 编码的一种方法

.thumb
.syntax unified

b skip
nop
nop
nop
nop
nop
skip:

00000000 <skip-0xe>:
   0:   f000 b805   b.w e <skip>
   4:   46c0        nop         ; (mov r8, r8)
   6:   46c0        nop         ; (mov r8, r8)
   8:   46c0        nop         ; (mov r8, r8)
   a:   46c0        nop         ; (mov r8, r8)
   c:   46c0        nop         ; (mov r8, r8)

分支的 T4 编码是第一个半字顶部的 11110,表示这是未定义的指令(不是 ARMv6T2、ARMv7 的任何指令)或 ARMv6T2、ARMv7 的 thumb2 扩展

第二个半字 10x1,我们看到一个 B,所以看起来不错这是一个 thumb2 扩展分支。

S是0 imm10是0 j1是1 j2是1 imm11是5

I1 = NOT(J1 EOR S); I2 = NOT(J2 EOR S); imm32 = SignExtend(S:I1:I2:imm10:imm11:’0’, 32);

1 EOR 0 是 1 对吗?不是说你得到 0。所以 I1 和 I2 都是零 s 是零 imm10 是零。所以我们基本上只把 imm11 看成一个正数

执行时pc超前四所以so 0xE - 0x4 = 0xA.

0xA / 2 = 0x5 那就是我们的分支偏移量 offset pc + (5*2)

.syntax unified
.thumb


b.w skip
nop
here:
nop
nop
nop
nop
skip:
b.w here

00000000 <here-0x6>:
   0:   f000 b805   b.w e <skip>
   4:   46c0        nop         ; (mov r8, r8)

00000006 <here>:
   6:   46c0        nop         ; (mov r8, r8)
   8:   46c0        nop         ; (mov r8, r8)
   a:   46c0        nop         ; (mov r8, r8)
   c:   46c0        nop         ; (mov r8, r8)

0000000e <skip>:
   e:   f7ff bffa   b.w 6 <here>

s是1,imm10是0x3FF j1是1 j2是1 imm1是0x7FA

1 eor 1 是 0 并不是说​​ i1 为 1 而 i2 为 1

imm32 = SignExtend(S:I1:I2:imm10:imm11:’0’, 32);

s 是 1,所以这将对 1 进行符号扩展,但最后几位是 1,因此 imm32 是 0xFFFFFFFA 或 -6 指令返回或 -12 字节返回

所以我们的偏移量也是 ((0xE + 4) - 6)/2 = 6。或者换个角度看 从指令编码 PC - (6*2) = (0xE + 4) - 12 = 6 分支到 0x6.

所以如果你想分支说 0x70 并且指令的地址是 0x12 那么你的偏移量是 0x70-(0x12+4) = 0x62 或 0x31 指令,我们从跳过中知道技巧是使 s 0 和 j1 和 j2 一个 1

0x12: 0xF000 0xB831  branch to 0x70

所以现在知道我们可以回到这个:

0:  e7fe        b.n 50 <*ABS*0x50>

偏移量是符号扩展0x7FE 或0xFFFFFFFE。 0xFFFFFFFE*2 + 4 = 0xFFFFFFFC + 4 = 0x00000000。分支到 0

添加一个 nop

.thumb
nop
b 0x50

00000000 <.text>:
   0:   46c0        nop         ; (mov r8, r8)
   2:   e7fe        b.n 50 <*ABS*0x50>

相同的编码

所以反汇编意味着 0x50 的绝对值但没有对其进行编码,链接无济于事它只是抱怨

(.text+0x0): relocation truncated to fit: R_ARM_THM_JUMP11 against `*ABS*0x50'

这个

.thumb
nop
b 0x51

给出相同的编码。

所以基本上这个语法有问题 and/or 它正在寻找一个名为 0x50 的标签?

我希望你的例子是你想知道某个地址的分支编码而不是那个确切的语法。

arm不像其他一些指令集,分支总是相对的。因此,如果您可以根据编码到达目的地,那么您将获得一个分支,否则,您必须使用 bx 或 pop 或其他方法之一来修改 pc(具有绝对值)。

从文档中知道T2编码只能提前到2048,然后在分支和目的地之间放置2048多个nop

b.s: Assembler messages:
b.s:5: Error: branch out of range

也许这就是您想要做的?

.thumb
mov r0,#0x51
bx r0

00000000 <.text>:
   0:   2051        movs    r0, #81 ; 0x51
   2:   4700        bx  r0

分支到绝对地址 0x50。对于该特定地址,不需要 thumb2 扩展。

.thumb
ldr r0,=0x12345679
bx r0
00000000 <.text>:
   0:   4800        ldr r0, [pc, #0]    ; (4 <.text+0x4>)
   2:   4700        bx  r0
   4:   12345679    eorsne  r5, r4, #126877696  ; 0x7900000

分支到地址 0x12345678 或任何其他可能的地址。

谢谢@dwelch但我不太明白你的意思。我为我的无知道歉...

我尝试 encode/decode B 指令使用按位运算,尽管非常简单和愚蠢 :) 下面的代码现在似乎可以工作了。 @Jester

#define MAX_CODE_LEN 4
typedef unsigned char uchar;
typedef unsigned int uint;

static int decode_B_T4(const int code)
{
    const int S = (code & (1 << 26)) ? 1 : 0;      /* test bit [26] */
    const int J1 = (code & (1 << 13)) ? 1 : 0;     /* test bit [13] */
    const int J2 = (code & (1 << 11)) ? 1 : 0;     /* test bit [11] */
    const int imm10 = (code >> 16) & 0b1111111111; /* extract imm10 */
    const int imm11 = code & 0b11111111111;        /* extract imm11 */
    const int I1 = (~(J1 ^ S)) & 1;
    const int I2 = (~(J2 ^ S)) & 1;
    int offset = 0;
    offset |= I1 << 23;
    offset |= I2 << 22;
    offset |= imm10 << 12;
    offset |= imm11 << 1;
    if (S) {
        offset |= 0b11111111 << 24;               /* sign extend */
    }
    return offset;
}

static int encode_B_T4(const int src_addr, const int dst_addr, uchar* buf)
{
    assert(buf != NULL);
    uint code;
    const int code_len = 4;                           /* 4 bytes */
    const int offset = (dst_addr & (~1)) - (src_addr & (~1)) - 4;
    const int S = offset < 0;                         /* sign */
    const int I1 = offset & (1 << 23) ? 1 : 0;        /* test bit [23] */
    const int I2 = offset & (1 << 22) ? 1 : 0;        /* test bit [22] */
    const int imm10 = (offset >> 12) & 0b1111111111;  /* extract imm10 */
    const int imm11 = (offset >> 1) & 0b11111111111;  /* extract imm11 */
    const int J1 = ((~I1 & 1) ^ S) & 1;
    const int J2 = ((~I2 & 1) ^ S) & 1;
    code = 0b11110 << 27;                             /* set the 5 MSB */
    code |= S << 26;
    code |= imm10 << 16;
    code |= 1 << 15;
    code |= J1 << 13;
    code |= 1 << 12;
    code |= J2 << 11;
    code |= imm11;
    assert(code_len <= MAX_CODE_LEN);
    memcpy(buf, &code, code_len);
    return code_len;
}