.data
.global _start
_start:
mov r7, #4
mov r0, #1
mov r2, #12
ldr r4, =#0x6c6c6548
str r4, [pc, #4]
mov r1, pc
add pc, pc, #8
strbt r6, [ip], -r8, asr #10
svcvs 0x0057206f
beq 0x193b248
swi #0
mov r7, #1
mov r0, #0
swi #0
我偶然发现了这个小型ARM组装程序,它打印出“Hello World”。将其另存为test.s
以进行测试:
$ as -o test.o test.s
$ ld -o test test.o
$ ./test
Hello World
$
这是如何工作的?我在整个程序中看不到一个字符串。它也不会从其他任何地方读取字符串;看起来这个代码就是打印字符串所需的全部内容。字符串来自哪里?
答案 0 :(得分:1)
这是有趣位的注释:
mov r7, #4
mov r0, #1
mov r2, #12
ldr r4, =#0x6c6c6548
A str r4, [pc, #4]
B mov r1, pc
C add pc, pc, #8
D strbt r6, [ip], -r8, asr #10
E svcvs 0x0057206f
F beq 0x193b248
G swi #0
mov r7, #1
mov r0, #0
swi #0
A
处的商店定位地点D
- 正如评论中所指出的那样,该字(以小端序排列)创建了4个ASCII字节“地狱” - 存储在顶部那里的无意义指令(机器代码是0xe66c6548 - 关闭,但不够好)。这可能也是数据部分的原因,以确保它是可写的 * 。同时,E
处的指令的机器代码是0x6f57206f,这使得“o Wo”。指令F
特别棘手,因为该地址必须导致相对分支偏移,一旦编码,看起来像“rld” ** - beq
编码是0x0annnnnn,其中nnnnnn是26位二进制补码偏移值的前24位 - 请注意,顶部字节中的条件代码和操作码构成最终换行符。
指令B
将D
的地址放入r1,即指向字符串开头的指针。 r0和r2显然是其他必要的系统调用参数,而r7是系统调用号本身(我懒得查找它,但我假设r0中的1是stdout,r2中的12是字符数,和系统调用4是write
)。
最后,说明C
是G
处系统调用的跳转,因此D
,E
和F
都没有“说明”实际上是执行的(其余的只是在进行exit
系统调用)。
非常简洁,适用于特技代码。
*并且可能还依赖于加载器中的一些向后兼容性行为,使数据部分可执行。
**偶然发生在我的binutils 2.26链接器上,可能是由于最近版本中默认的部分对齐已经改变。