My solution to https://codegolf.stackexchange.com/a/217992/94093
.text
.arch armv5te
.arm
.globl _start
_start:
ldr r0, =0xe38f0001
ldr r0, =0xe12fff10
ldr r0, =0xa1032001
ldr r0, =0x27042207
ldr r0, =0x2701df00
ldr r0, =0xdf002000
ldr r0, =0x42737062
ldr r0, =0x002e5350
The way we solve this problem is with the ldr
pseudo-instruction:
ldr r0, =constant
If the assembler can't translate this to a mov
,
it will place it in a pool after the last instruction
and use a PC relative load.
Therefore, we can just encode the instructions manually inside the literal pool.
We must switch to Thumb first, as the syscall
instruction, svc #0
, is encoded as 0xef000000
,
which can be encoded in a mov
. Thumb instructions
are also half the size. 😉
The ldr
s themselves just set r0
to nonsense values,
and are, for our intents and purposes, a nop
sled.
After the ldr
sled, the constant pool will be interpreted
as the following code:
.arm
real_start:
orr r0, pc, #1
bx r0
.thumb
.Lthumb_start:
movs r0, #1
adr r1, .Lstr
movs r2, #7
movs r7, #4
svc #0 // write(1, .Lstr, 7)
movs r7, #1
movs r0, #0
svc #0 // exit(0)
.Lstr:
.asciz "bpsBPS."
The first two instructions are a typical "ARM to Thumb"
springboard. Whenever bx
, pop {pc}
, bl
, or blx
attempt
to jump to an odd address, the CPU will switch to Thumb and
read 16-bit instructions instead.
Note that the PC points two instructions ahead of the current
instruction, that is why we just |1
instead of +9
.
orr r0, pc, #1
bx r0
After that, it is a pretty standard write()
followed by exit()
,
using svc #0
for syscalls.