【问题标题】:How to generate the machine code of Thumb instructions?如何生成 Thumb 指令的机器码?
【发布时间】:2016-02-16 16:08:17
【问题描述】:

我在谷歌上搜索过生成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 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 来自哪里,以及如何用它们构造完整的机器代码。

【问题讨论】:

  • 更仔细地查看编码的解码伪代码,特别是imm32 = SignExtend(S:I1:I2:imm10:imm11:'0', 32);,您可以从中反向工作到特定的编码。或者作弊去看看 binutils、LLVM 或任何其他开源汇编程序的源代码......
  • 我试过这种方法。 imm32 是 B 指令的相对地址,所以我倒推计算了 S, I1, I2, imm10, imm11 字段。与 gcc 生成的机器码相比,我的工作是偶然的。也许我的算法是错误的...... GCC 本身就是一个大项目。这里airs.com/dnovillo/200711-GCC-Internals 是关于 gcc 的一面。它将(源)代码转换为直接代码,然后编译......我想要的只是一个从 ARM 程序集生成机器代码的简单函数。
  • 您能否展示您的代码以及它不起作用的情况?
  • AFAIK GCC 本身不会生成机器代码,但仍会将中间文件交给外部汇编器,因此如果您想编写汇编器,这几乎无关紧要。这就是为什么我将 binutils 命名为 GNU assembler 所在的项目。浏览代码大约需要 15 分钟才能找到 the relevant bit,这似乎是可以理解的(我之前没有使用 binutils 源代码的经验)。
  • 您想知道立即字段是如何围绕指令拆分的还是什么?请记住,在 ARM 模式下,PC 提前 2 个字(8 个字节),而在拇指模式下,提前 2 个半字(4 个字节)。因此,对于位置 0x8000 的分支,您从地址 0x8004 开始计算拇指。

标签: assembly compilation arm machine-code thumb


【解决方案1】:

首先,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 领先四位,所以 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 一个 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 或任何其他可能的地址。

【讨论】:

  • 所有这些都压缩成这个。如果目标处理器已经有一个工作的汇编程序,请使用它,尤其是分支。如果没有工作的汇编程序并且您必须对指令进行编码,那么请走到处理处理器的工程师的立方体并询问他们。和/或在那里工作时查看处理器的源代码。
【解决方案2】:

谢谢@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;
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2014-04-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-03-07
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多