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

时间:2015-11-15 13:54:33

标签: assembly compilation arm machine-code thumb

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

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

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

B instruction, encoding T4

汇编代码:

B 0x50

如何将立即值0x50编码为4字节机器码?或者,如果我想编写一个带有B指令和输入的C函数,并返回编码的机器代码。我该如何实现这样的功能?

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来自哪里,以及如何使用它们构建完整的机器代码。

2 个答案:

答案 0 :(得分:3)

首先,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的一个thumb2扩展,ARMv7

第二个半字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是零。所以我们基本上只是把这个看作是一个正数

当执行时,pc是四个,所以0xE - 0x4 = 0xA。

0xA / 2 = 0x5,这是我们的分支偏移量偏移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为

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 a 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

给出相同的编码。

所以基本上这个语法有问题和/或它正在寻找一个名为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或任何其他可能的地址。

答案 1 :(得分:0)

谢谢@dwelch,但我不太了解你。我为自己的无知而道歉......

我尝试使用按位运算对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;
}