assembly - 这个汇编程序如何打印 "Hello World"?

标签 assembly arm

.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
$

这是如何工作的?我在整个程序中看不到任何一个字符串。它也不会从其他地方读取字符串;看起来这段代码就是打印字符串所需的全部代码。字符串从哪里来?

最佳答案

这是一个有趣的注释:

  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 字节“Hell” -它存储在无意义指令的顶部(其机器代码是 0xe66c6548 - 接近,但不够好)。这大概也是为什么它位于数据部分的原因,以确保它是可写的*。同时,E处指令的机器码为0x6f57206f,即“o Wo”。指令 F 特别棘手,因为该地址必须产生相对分支偏移量,一旦编码,看起来像“rld”** - beq 编码为 0x0annnnnn,其中 nnnnnn 是 26 位二进制补码偏移值的前 24 位 - 另请注意,顶部字节中的条件代码和操作码构成了最终的换行符。

指令BD的地址放入r1,即指向字符串开头的指针。 r0 和 r2 显然是其他必要的系统调用参数,而 r7 是系统调用号本身(我懒得查找它,但我假设 r0 中的 1 用于 stdout,r2 中的 12 是字符数,系统调用 4 是write)。

最后,指令C是跳转到G处的系统调用,因此D处没有任何“指令”, EF 实际上被执行(之后的其余部分只是进行 exit 系统调用)。

非常简洁,适合技巧代码。

* 并且可能还依赖加载程序中的某些向后兼容行为来使数据部分可执行。

** 这在我的 binutils 2.26 链接器中不会发生,可能是由于最近版本中默认的节对齐方式发生了变化。

关于assembly - 这个汇编程序如何打印 "Hello World"?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/38294896/

相关文章:

c++ - mbed uvisor 动态加载二进制文件

c - GCC:为什么 const 数据填充在我的函数内部而不是开头?

汇编:Y86 堆栈和调用、pushl/popl 和 ret 指令

assembly - MIPS 32 位无符号乘法,不使用 mult 或 div

c - python 中参数/变量的存储限制

assembly - 我们看到一个进程的虚拟地址(在分页系统中),这些虚拟地址存在哪里?

linux - ARM Linux 上的 Flash Player 调试?

c - 如何使用 Neon SIMD 将无符号字符转换为有符号整数

assembly - 内部搬迁未解决

c++ - 这个指令错了吗? (movabs %al,0xe400000000004049)