Skip to content

Instantly share code, notes, and snippets.

@ForceBru
Last active April 11, 2022 13:59
Show Gist options
  • Save ForceBru/e5c487607342cbd853bdb2e31cd5fcd8 to your computer and use it in GitHub Desktop.
Save ForceBru/e5c487607342cbd853bdb2e31cd5fcd8 to your computer and use it in GitHub Desktop.
Weird encoding of the BL instruction on ARM Thumb

Result of assembling on different platforms

Apparently, both GCC and Clang encode bl label in such a way that the resulting machine code ends up jumping to itself, not label. However, when assembled on an actual thumbv7 machine, the machine code suddenly becomes correct. What's more, in many cases of "incorrect" encoding that should jump to itself, objdump somehow recognizes that it jumps to the correct label.

armv7-alpine-linux-musleabihf

Put Dockerfile and mve_docker.s in the same directory and run:

docker build --tag arm_bl . && docker run --rm arm_bl

SEE COMMENTS next to bl instructions!

gcc (Alpine 9.3.0) 9.3.0
Copyright (C) 2019 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

armv7-alpine-linux-musleabihf
Alpine clang version 10.0.0 (https://gitlab.alpinelinux.org/alpine/aports.git 7445adce501f8473efdb93b17b5eaf2f1445ed4c)
Target: armv7-alpine-linux-musleabihf
Thread model: posix
InstalledDir: /usr/bin

gcc_mve_docker.obj - object file

00000000 <_factorial>:
   0:	b500      	push	{lr}
   2:	2800      	cmp	r0, #0
   4:	d008      	beq.n	18 <L0_return_one>
   6:	b401      	push	{r0}
   8:	f1a0 0001 	sub.w	r0, r0, #1
   c:	f7ff fffe 	bl	0 <_factorial>  # ENCODING: f7ff fffe (ends up jumping to same address! yet objdump puts the correct label)
  10:	bc02      	pop	{r1}
  12:	fb00 f001 	mul.w	r0, r0, r1
  16:	e001      	b.n	1c <L1_exit>

gcc_mve_docker.bin - executable

000004c0 <_factorial>:
 4c0:	b500      	push	{lr}
 4c2:	2800      	cmp	r0, #0
 4c4:	d008      	beq.n	4d8 <L0_return_one>
 4c6:	b401      	push	{r0}
 4c8:	f1a0 0001 	sub.w	r0, r0, #1
 4cc:	f7ff fff8 	bl	4c0 <_factorial>  # ENCODING: f7ff fff8 (runs fine, checked in Dockerfile)
 4d0:	bc02      	pop	{r1}
 4d2:	fb00 f001 	mul.w	r0, r0, r1
 4d6:	e001      	b.n	4dc <L1_exit>

clang_mve_docker.obj

00000000 <_factorial>:
   0:	b500      	push	{lr}
   2:	2800      	cmp	r0, #0
   4:	d008      	beq.n	18 <L0_return_one>
   6:	b401      	push	{r0}
   8:	f1a0 0001 	sub.w	r0, r0, #1
   c:	f7ff fffe 	bl	0 <_factorial>  # ENCODING: f7ff fffe (jumps to itself, but the label is correct)
  10:	bc02      	pop	{r1}
  12:	fb00 f001 	mul.w	r0, r0, r1
  16:	e001      	b.n	1c <L1_exit>

clang_mve_docker.bin

000004c0 <_factorial>:
 4c0:	b500      	push	{lr}
 4c2:	2800      	cmp	r0, #0
 4c4:	d008      	beq.n	4d8 <L0_return_one>
 4c6:	b401      	push	{r0}
 4c8:	f1a0 0001 	sub.w	r0, r0, #1
 4cc:	f7ff fff8 	bl	4c0 <_factorial>  # ENCODING: f7ff fff8 (works fine)
 4d0:	bc02      	pop	{r1}
 4d2:	fb00 f001 	mul.w	r0, r0, r1
 4d6:	e001      	b.n	4dc <L1_exit>

thumbv7-apple-darwin

When assembled for thumbv7-apple-darwin on that platform (old iPhone 4) by Clang 3.7 and Clang 9.0, both object files and executables contain exactly the same assembly - the one where bl _factorial is fff7 f8ff (note that the words are flipped); both executables run correctly.

# Compile and run `mve_docker.s` above on native ARMv7 Thumb
FROM arm32v7/alpine:latest
ENV test_file=mve_docker
ENV file_obj="$test_file".obj
ENV file_exe="$test_file".bin
WORKDIR /home
COPY $test_file.s .
RUN apk add build-base clang
RUN gcc $test_file.s -o gcc_$file_obj -c && \
gcc $test_file.s -o gcc_$file_exe && \
{ ./gcc_$file_exe && exit 1 || echo "Correct: $?"; } && \
clang $test_file.s -o clang_$file_obj -c && \
clang $test_file.s -o clang_$file_exe && \
{ ./clang_$file_exe && exit 1 || echo "Correct: $?"; }
CMD gcc --version && \
gcc -dumpmachine && \
clang --version && \
echo -e "\n--------------------\n" && \
echo "FILE: gcc_$file_obj" && \
objdump -d gcc_$file_obj | sed '/<_factorial>:/,/^$/!d' && \
echo "FILE: gcc_$file_exe" && \
objdump -d gcc_$file_exe | sed '/<_factorial>:/,/^$/!d' && \
echo "FILE: clang_$file_obj" && \
objdump -d clang_$file_obj | sed '/<_factorial>:/,/^$/!d' && \
echo "FILE: clang_$file_exe" && \
objdump -d clang_$file_exe | sed '/<_factorial>:/,/^$/!d'
# Computes the factorial of 5
.arch armv7-a
.syntax unified
.globl _factorial
.align 1
.thumb
.thumb_func
.type _factorial, %function
_factorial:
push {lr}
cmp r0, #0
beq L0_return_one
push {r0}
sub r0, r0, #1
bl _factorial # THIS is the instruction of interest
pop {r1}
mul r0, r0, r1
b L1_exit
L0_return_one:
mov r0, 1
L1_exit:
pop {pc}
.globl main
.align 1
.thumb
.thumb_func
.type main, %function
main:
push {lr}
mov r0, 5
bl _factorial
pop {pc}
@ForceBru
Copy link
Author

@msbit, yes, this seems to be the issue. That's also what I was told on Stack Overflow: https://stackoverflow.com/questions/62105226/arm-thumb-bl-instruction-loops-to-itself. Makes total sense now.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment