Bug 28924 - ld-arm bug in encoding of blx calls jumping from thumb to arm instructions
Summary: ld-arm bug in encoding of blx calls jumping from thumb to arm instructions
Status: RESOLVED FIXED
Alias: None
Product: binutils
Classification: Unclassified
Component: ld (show other bugs)
Version: 2.37
: P2 normal
Target Milestone: ---
Assignee: Not yet assigned to anyone
URL:
Keywords:
Depends on:
Blocks:
 
Reported: 2022-02-25 08:53 UTC by Jori Bomanson
Modified: 2022-03-18 15:43 UTC (History)
2 users (show)

See Also:
Host:
Target:
Build:
Last reconfirmed:


Attachments
Small source file for reproducing the bug (354 bytes, text/plain)
2022-02-25 08:53 UTC, Jori Bomanson
Details
Binary object file that reveals the bug (1.05 KB, application/x-zip-compressed)
2022-03-16 16:10 UTC, Viorel Preoteasa
Details
Disassembled erroneous file after linking the object file (538 bytes, text/plain)
2022-03-16 16:12 UTC, Viorel Preoteasa
Details
Disassembled correct file after using the fixed ld (560 bytes, text/plain)
2022-03-16 16:13 UTC, Viorel Preoteasa
Details

Note You need to log in before you can comment on or make changes to this bug.
Description Jori Bomanson 2022-02-25 08:53:49 UTC
Created attachment 13996 [details]
Small source file for reproducing the bug

We encountered a linking error in 11.1 and 11.2 of the ARM GNU Toolchain. There seems to be a bug in encodings of calls using blx when jumping from thumb to arm instructions. When the jump is exactly 2^24 + 2, the jump turns into a jump of 2 instead of an indirect jump. If the jump is shorter, a direct jump is generated correctly. If the jump is longer, an indirect jump is generated correctly.

The bug can be reproduced for example on the ARM GNU Toolchain version 11.2-2022.02 for the AArch32 bare-metal target (arm-none-eabi) available for x86_64 Linux hosted cross toolchains here: [Arm GNU Toolchain | Arm GNU Toolchain Downloads – Arm Developer](https://developer.arm.com/tools-and-software/open-source-software/developer-tools/gnu-toolchain/downloads).

A small example for triggering this bug for the above toolchain is attached as "test.cpp". For other versions of the toolchain, the example needs to be tweaked, because it is sensitive to code size, which typically differs between versions.

The bug goes away if `ERROR_CALL` in "test.cpp" is replaced by either of the commented parts `//DIRECT_CALL` or `//INDIREC_CALL`.

To reproduce this bug, the file "test.cpp" can be compiled with:
arm-none-eabi-g++ -std=gnu++17 -mcpu=cortex-a9 -mfpu=vfpv3 -fdata-sections -ffunction-sections -mfloat-abi=hard -O3 -save-temps=obj -fverbose-asm --specs=nosys.specs test.cpp

Now if one disassembles the output using `arm-none-eabi-objdump -d a.out > a.s`, then the resulting file a.s contains the following encoding of the first call to f():
8036: f000 e800 blx 8038 <main+0x8>

This instruction represents a jump of two bytes forward to the address 8036 to 8038, which is wrong.
Comment 1 Nick Clifton 2022-03-16 13:21:33 UTC
(In reply to Jori Bomanson from comment #0)
Hi Jori,
 
> A small example for triggering this bug for the above toolchain is attached
> as "test.cpp".

Would it be possible for you to create a test case using assembler
source files, rather than C source files ?  The output from gcc tends
to be very sensitive to the version of the compiler being used, so 
attempts to reproduce the problem may not always work.  (Especially
if the reproducer does not wish to download and install the ARM GNU
Toolchain).
 
 
Also - have you tested this problem using the newly released 2.38
version of the binutils sources ?  It may be possible that this bug
has already been fixed...

Cheers
  Nick
Comment 2 Viorel Preoteasa 2022-03-16 16:10:05 UTC
Created attachment 14019 [details]
Binary object file that reveals the bug
Comment 3 Viorel Preoteasa 2022-03-16 16:12:21 UTC
Created attachment 14020 [details]
Disassembled erroneous file after linking the object file
Comment 4 Viorel Preoteasa 2022-03-16 16:13:09 UTC
Created attachment 14021 [details]
Disassembled correct file after using the fixed ld
Comment 5 Viorel Preoteasa 2022-03-16 16:55:19 UTC
The error occurs also in the master branch of binutils. The bug can be reproduced applying ld 
compiled for arm using the following configuration:

export CPPFLAGS=-I/home/viorel/arm-none-eabi-original/buildNative/prerequisites/zlib-1.2.11/include -pipe
export LDFLAGS=-L/home/viorel/arm-none-eabi-original/buildNative/prerequisites/zlib-1.2.11/lib

/home/viorel/arm-none-eabi-original/sources/binutils-master/configure \
  --build=x86_64-pc-linux-gnu \
  --host=x86_64-pc-linux-gnu \
  --with-python=yes \
  --target=arm-none-eabi \
  --prefix=/home/viorel/arm-none-eabi-original/installNative \
  --docdir=/home/viorel/arm-none-eabi-original/installNative/share/doc \
  --enable-lto \
  --enable-gold \
  --disable-werror CPPFLAGS=-UFORTIFY_SOURCE \
  --disable-gdb \
  --disable-sim \
  --disable-libdecnumber \
  --disable-readline \
  --disable-nls \
  --enable-plugins \
  --with-system-zlib \
  "--with-pkgversion=none-GCC-11.2.1-2022-03"
  
This assumes that zlib is compiled appropriately.

The binary object file (attached in zip format) must be linked using:

arm-none-eabi-ld test-ld.o -o test-ld.bin

And the result can be explored using:

arm-none-eabi-objdump -d test-ld.bin > test-ld.s

When using the unmodified master branch (22546800ad34a5ac6bc90e6701de3e74bad75551), 
the resulting file test-ld.s contains on line 9 the following assembly instruction:

    8002:	f000 e800 	blx	8004 <main+0x4>
	
This is obviously a wrong jump, as it is in the middle of the current instruction. 
The address 8004 is in the middle of current instruction "blx 8004".
	
The corrected ld applied to the same object file results in:

    8002:	f000 e810 	blx	8024 <___Z1fv_from_thumb>

The bug is in the file bfd/elf32-arm.c at lines:
#define THM_MAX_FWD_BRANCH_OFFSET  ((1 << 22) -2 + 4)
#define THM2_MAX_FWD_BRANCH_OFFSET (((1 << 24) - 2) + 4)

These must be replaced by:
#define THM_MAX_FWD_BRANCH_OFFSET  ((1 << 22) -4 + 4)
#define THM2_MAX_FWD_BRANCH_OFFSET (((1 << 24) - 4) + 4)

Next there is a diff file with these changes as well as with some explanations.

diff --git a/bfd/elf32-arm.c b/bfd/elf32-arm.c
index 616efe60..109c919d 100644
--- a/bfd/elf32-arm.c
+++ b/bfd/elf32-arm.c
@@ -2536,11 +2536,22 @@ static const bfd_vma elf32_arm_nacl_plt_entry [] =
   0xea000000,          /* b    .Lplt_tail                      */
 };

+/* There was a bug due to too high values of THM_MAX_FWD_BRANCH_OFFSET and
+   THM2_MAX_FWD_BRANCH_OFFSET. The first macro concerns the case when Thumb-2 is
+   not available, and secod macro when Thumb-2 is available. Among other things, they affect the range
+   of branches represented as blx instructions in Encoding T2 defined in Section
+   A8.8.25 of the ARM Architecture Reference Manual ARMv7-A and ARMv7-R
+   edition issue C.d. Such branches are specified there to have a maximum
+   forward offset that is a multiple of 4. Previously, the respective values
+   defined here were multiples of 2 but not 4 and they are included in comments
+   for reference. */
 #define ARM_MAX_FWD_BRANCH_OFFSET  ((((1 << 23) - 1) << 2) + 8)
 #define ARM_MAX_BWD_BRANCH_OFFSET  ((-((1 << 23) << 2)) + 8)
-#define THM_MAX_FWD_BRANCH_OFFSET  ((1 << 22) -2 + 4)
+#define THM_MAX_FWD_BRANCH_OFFSET  ((1 << 22) - 4 + 4)
+/* #define THM_MAX_FWD_BRANCH_OFFSET  ((1 << 22) -2 + 4) */
 #define THM_MAX_BWD_BRANCH_OFFSET  (-(1 << 22) + 4)
-#define THM2_MAX_FWD_BRANCH_OFFSET (((1 << 24) - 2) + 4)
+#define THM2_MAX_FWD_BRANCH_OFFSET (((1 << 24) - 4) + 4)
+/* #define THM2_MAX_FWD_BRANCH_OFFSET (((1 << 24) - 2) + 4) */
 #define THM2_MAX_BWD_BRANCH_OFFSET (-(1 << 24) + 4)
 #define THM2_MAX_FWD_COND_BRANCH_OFFSET (((1 << 20) -2) + 4)
 #define THM2_MAX_BWD_COND_BRANCH_OFFSET (-(1 << 20) + 4)
Comment 6 Sourceware Commits 2022-03-18 15:33:04 UTC
The master branch has been updated by Nick Clifton <nickc@sourceware.org>:

https://sourceware.org/git/gitweb.cgi?p=binutils-gdb.git;h=a747a286b9331adfba85a508beddaef47ea1a1c2

commit a747a286b9331adfba85a508beddaef47ea1a1c2
Author: Viorel Preoteasa <viorel.preoteasa@gmail.com>
Date:   Fri Mar 18 15:32:28 2022 +0000

    Fix ld-arm bug in encoding of blx calls jumping from thumb to arm instructions
    
            PR 28924
            * elf32-arm.c (THM_MAX_FWD_BRANCH_OFFSET): Fix definition.
            (THM2_MAX_FWD_BRANCH_OFFSET): Likewise.
Comment 7 Nick Clifton 2022-03-18 15:43:16 UTC
Hi Viorel,

  Thanks for the patch - I have gone ahead and applied it.

Cheers
  Nick