Python ARM Thumb BL指令循环到自身

Python ARM Thumb BL指令循环到自身,python,python-3.x,arm,thumb,Python,Python 3.x,Arm,Thumb,我正在尝试使用Keystone组装此代码,并使用Unicorn引擎执行它: start: add r0, r0, #1 add r1, r1, #2 bl start b start 我认为,bl指令应该将下一条指令的地址保存到lr寄存器,然后跳转到start。因此,这将是一个无限循环,它将1添加到r0,将2添加到r1 显然,我错了,因为bl start转而分支到自身 我正在使用Keystone、Capstone和Unicorn的Python包装器来处理程序集。

我正在尝试使用Keystone组装此代码,并使用Unicorn引擎执行它:

start:
    add r0, r0, #1
    add r1, r1, #2
    bl start
    b start
我认为,
bl
指令应该将下一条指令的地址保存到
lr
寄存器,然后跳转到
start
。因此,这将是一个无限循环,它将
1
添加到
r0
,将
2
添加到
r1

显然,我错了,因为
bl start
转而分支到自身

我正在使用Keystone、Capstone和Unicorn的Python包装器来处理程序集。这是我的密码:

import keystone as ks
import capstone as cs
import unicorn as uc

print(f'Keystone {ks.__version__}\nCapstone {cs.__version__}\nUnicorn {uc.__version__}\n')


code = '''
start:
    add r0, r0, #1
    add r1, r1, #2
    bl start
    b start
'''

assembler = ks.Ks(ks.KS_ARCH_ARM, ks.KS_MODE_THUMB)
disassembler = cs.Cs(cs.CS_ARCH_ARM, cs.CS_MODE_THUMB)
emulator = uc.Uc(uc.UC_ARCH_ARM, uc.UC_MODE_THUMB)

machine_code, _ = assembler.asm(code)
machine_code = bytes(machine_code)
print(machine_code.hex())

initial_address = 0
for addr, size, mnem, op_str in disassembler.disasm_lite(machine_code, initial_address):
    instruction = machine_code[addr:addr + size]
    print(f'{addr:04x}|\t{instruction.hex():<8}\t{mnem:<5}\t{op_str}')

emulator.mem_map(initial_address, 1024)  # allocate 1024 bytes of memory
emulator.mem_write(initial_address, machine_code)  # write the machine code
emulator.hook_add(uc.UC_HOOK_CODE, lambda uc, addr, size, _: print(f'Address: {addr}'))
emulator.emu_start(initial_address | 1, initial_address + len(machine_code), timeout=500)

编辑

我接着用叮当声汇编了这段代码:

; test.s

.text
.syntax unified
.globl  start       
.p2align    1
.code   16       
.thumb_func
start:
    add r0, r0, #1
    add r1, r1, #2
    bl start
    b start
使用以下命令:

$ clang -c test.s -target armv7-unknown-linux -o test.bin -mthumb
clang-11: warning: unknown platform, assuming -mfloat-abi=soft
然后用
objdump
反汇编
test.bin

$ objdump -d test.bin

test.bin:       file format elf32-littlearm


Disassembly of section .text:

00000000 <start>:
       0: 00 f1 01 00                   add.w   r0, r0, #1
       4: 01 f1 02 01                   add.w   r1, r1, #2
       8: ff f7 fe ff                   bl      #-4
       c: ff f7 fe bf                   b.w     #-4 <start+0x10>
$ 
$objdump-d test.bin
bin:文件格式elf32 littlearm
第节的分解。正文:
00000000 :
0:00 f1 01 00 add.w r0,r0,#1
4:01 f1 02 01 add.w r1,r1,#2
8:ff f7 fe ff bl#-4
c:ff f7 fe bf b.w#-4
$ 
所以
bl
的参数实际上是一个偏移量。这是负面的,因为我们在倒退。但是:

对于
B
BL
CBNZ
CBZ
指令,PC的值是当前指令的地址加上4个字节

因此,
bl#-4
将跳转到
(bl的地址)+4字节-4字节,或者,换句话说,再次跳转到它本身

所以,出于某种原因,我不能向后
bl
?这里发生了什么以及如何修复它?

所有工具“链”链接器都必须处理函数调用或其他到外部资源的操作,您将看到像bl这样的指令编码为分支到自身或分支到零或一些这样的不完整指令(当然是对于外部标签)。与此相切的是,某些版本的clang有时似乎为本地地址编码,有时则不是(在汇编程序级别)。但当链接时,偏移量/地址会被修补(如本例所示)

对象级别的通用clang(所有目标,默认x86主机)3.7给出了正确的指令。3.8没有。这似乎就是这一变化发生的时间。Clang10通用版没有,但是一个手工构建的Clang10.0.0特定于一个目标,在组装时给出了正确的答案

所有这些都是相切的,因为这是在装配时而不是最终输出。当链接时,您会得到正确的答案(到目前为止,OP可能还有其他没有得到正确答案的情况)

这与以前的一对thumb指令0xF7FF、0xFFFE是同一条指令,但对于armv7 ar,它被视为一条指令,不可分割 0xF7FFFFFE

感谢再次查找这个问题,我发现了这个问题,因为我要么知道了,要么忘记了,要么不知道

在ARMv6T2之前,编码T1和T2中的J1和J2均为1,因此分支范围较小。这些指令可以作为两条独立的16位指令执行

我已经在armv7之前的体系结构上演示了这两条指令是相互独立的,并表明它们不是一条指令

无论如何:

与来自gnu的指令相同

   8:   f7ff fffe   bl  0 <start>
8:f7ff fffe bl 0
GNUOne稍好一点,但仍有问题,编码不是BL0,但输出表明了最终的愿望,最后被重新编码,以便在链接时正确


因此,这些工具也可能是理解问题的一部分,因为它们无法以正确的可解码格式表示机器代码。

你找到答案了吗?找到一个不同的三元组,它有一个通用的叮当声可以工作?@old_timer,哦,天哪,我构建了一个递归的
factorial
函数,它在
armv7苹果达尔文
上使用了与叮当声3.7(!)相同的
bl
指令,它工作了,但生成的机器代码与这里的机器代码完全不同。我用三种不同版本的Clang为
armv7 none-eabi
重建了相同的程序集(遗憾的是,我没有地方运行它),所有这些程序都生成了
bl
指令,根据
objdump
,该指令将自动跳转。因此,唯一“正确”的版本是由iPhone的那个老铃声生成的。我不知道为什么,你答案中的程序集显示
f7ff fffa
作为
bl start
的机器代码,但在我的Keystone中,它被交换为
fff7 feff
(我想应该是
f7ff fffe
,但这仍然是
fe
,而不是
fa
)。看起来像是一个持久性问题。顺便说一句,我认为Keystone&friends实际上使用了Clang的代码来进行汇编和反汇编,所以我认为将其与“实际的”Clang进行比较是毫无意义的。。。不管怎样,只有老的Clang3.7产生了一些肯定有效的东西。明天,我将在《叮当声》中总结我对被诅咒的
bl
指令的研究,并更新这个问题。@old_timer,以下是我的一些发现:。看起来这与结果文件是对象文件或实际可执行文件有关。我想我已经看到了一个关于这个的问题,所以…DOH,没有想到,当然,一些工具链直到链接时才会解析,像这样的指令在任何情况下都肯定是由链接器处理的,所以为什么不让链接器一直这样做,即使汇编器知道答案。这可能很容易测试。很酷,所以这看起来像Keystone中的一个bug。我提交了-也许他们会修复它。谢谢你如此详细的回答!好吧,也许是机器代码的打印,但编码根本不是一个bug。这是典型的行为,并不少见。对于一个使用链接器的编译器来说,这可能很好,但据我所知,Keystone力求独立。我是说,Keystone甚至不能
$ objdump -d test.bin

test.bin:       file format elf32-littlearm


Disassembly of section .text:

00000000 <start>:
       0: 00 f1 01 00                   add.w   r0, r0, #1
       4: 01 f1 02 01                   add.w   r1, r1, #2
       8: ff f7 fe ff                   bl      #-4
       c: ff f7 fe bf                   b.w     #-4 <start+0x10>
$ 
.thumb
.syntax unified
.thumb_func
start:
    add r0, r0, #1
    add r1, r1, #2
    bl start
    b start

clang-3.8 -c so.s -target armv7-unknown-linux -o so.o
clang: warning: unknown platform, assuming -mfloat-abi=soft
arm-none-eabi-objdump -D so.o

so.o:     file format elf32-littlearm


Disassembly of section .text:

00000000 <start>:
   0:   f100 0001   add.w   r0, r0, #1
   4:   f101 0102   add.w   r1, r1, #2
   8:   f7ff fffe   bl  0 <start>
   c:   e7f8        b.n 0 <start>
arm-none-eabi-ld -Ttext=0 so.o -o so.elf
arm-none-eabi-ld: warning: cannot find entry symbol _start; defaulting to 0000000000000000
arm-none-eabi-objdump -d so.elf

so.elf:     file format elf32-littlearm


Disassembly of section .text:

00000000 <start>:
   0:   f100 0001   add.w   r0, r0, #1
   4:   f101 0102   add.w   r1, r1, #2
   8:   f7ff fffa   bl  0 <start>
   c:   e7f8        b.n 0 <start>
0008|   fff7feff    bl      #8         ; why not `bl #0`?

8: ff f7 fe ff                   bl      #-4
   8:   f7ff fffe   bl  0 <start>