When writing assembly code for ARM Cortex chips using Thumb instruction set, the BX and BLX instructions allow you to switch between Arm and Thumb states. Using them correctly is crucial for ensuring proper program execution flow. This comprehensive guide will provide developers with a deep understanding of how to utilize BX and BLX properly in Thumb code.
Quick Answer
To use BX and BLX correctly in Thumb code:
- Set bit 0 of the destination register to 1 for switching to Arm state, or 0 for staying in Thumb state.
- Use BLX with bit 0 set to 1 for calling Arm code from Thumb.
- Use BX with bit 0 set to 0 for returning to Thumb code from Arm.
- Clear bit 0 of LR before returning from Arm code to avoid incorrect execution flow.
- Use interworking veneers to transition between Arm and Thumb states if necessary.
BX Instruction
The BX (Branch and Exchange) instruction performs both a branch to a target address specified in a register, and changes the processor state if needed. The lowest bit of the register determines whether it stays in Thumb state or switches to Arm state.
For example: BX R0 ; Branch to address in R0 and stay in Thumb state
To switch to Arm state, bit 0 of the register should be set to 1. For example: ORR R0, R0, #1 BX R0 ; Switch to Arm state and branch to address in R0
Guidelines for Using BX
- If returning from an Arm function to Thumb code, clear bit 0 of the link register (LR) first before using BX.
- Use BX with bit 0 set to 0 when returning from an Arm exception handler back to Thumb code.
- Avoid using BX within a single Thumb function as it can corrupt LR.
- If using BX to return to Thumb code from an inline Arm function, store LR in another register first before clearing bit 0.
BLX Instruction
The BLX (Branch with Link and Exchange) instruction performs a branch to a target address specified in a register, changes the processor state if needed, and stores the return address in the LR (R14).
For example: BLX R1 ; Branch to address in R1, set LR, and stay in Thumb state
To switch to Arm state, bit 0 of the register should be set to 1. For example: ORR R1, R1, #1 BLX R1 ; Switch to Arm state, branch to R1, and set LR
Guidelines for Using BLX
- Use BLX with bit 0 set to call Arm code from Thumb.
- The LR will contain the Thumb return address so remember to clear bit 0 before returning.
- Avoid using BLX illegally within Thumb code as it can corrupt program flow.
- Use BLX instead of BX when calling Arm code to automatically set the link register.
Interworking Veneers
Since BX and BLX require setting bit 0 of the branch register, it is not possible to directly branch to Arm code from a Thumb literal address. Interworking veneers provide a solution to this by branching via a small stub function.
For example, the following veneer allows branching from Thumb code to the Arm function ‘foo’: arm_foo: BX PC thumb_code: BL arm_veneer … B arm_veneer arm_veneer: BX arm_foo
The veneer contains a single BX instruction to switch into Arm state and branch to the target Arm code. Veneers can work the other way too, allowing returns from Arm code back to Thumb.
Accessing High Registers
Some Arm instructions allow accessing registers R8-R12 which are not available in Thumb state. Switching to Arm with BX/BLX makes these registers available temporarily.
For example: BLX R1 ; R1 has bit 0 set ; Arm code can now access R8-R12 BX R0 ; R0 has bit 0 cleared
Interworking Branch Tables
Branch tables with both Thumb and Arm targets require special considerations. An interworking branch table contains both Thumb and Arm addresses, using bit 0 to distinguish them.
For example: ARM_func1: BX LR THUMB_func2: BX LR branchTable: .word THUMB_func2 .word ARM_func1 OR 1 ; bit 0 set for Arm target
The branch code must then clear bit 0 before branching to an Arm address from the table. This ensures a switch into Arm state. LDR R0, [PC, index, LSL #2] ; Load branch table entry BIC R0, R0, #1 ; Clear bit 0 BX R0 ; Branch to target
Position Independent Code (PIC)
For position independent Thumb code, follow these guidelines:
- Do not use BX or BLX with absolute literal addresses, as these are position dependent.
- Access absolute addresses via PC-relative loads instead.
- Use PC-relative branches like B.W instead of BX with literals whenever possible.
LDR R0, [PC, #offset] ; PC-relative load BX R0 ; Branch via register
Common Issues
Some common issues when using BX and BLX incorrectly:
- Forgetting to set bit 0 when switching from Thumb to Arm will keep code in Thumb state leading to failures or crashes.
- Neglecting to clear bit 0 of LR/branch registers before returning to Thumb code results in incorrect execution flow.
- Using BX or BLX illegally within a Thumb function corrupts the link register.
- Branching directly to an Arm address from Thumb code without an interworking veneer.
Summary
The BX and BLX instructions are vital for interfacing Thumb code with Arm code. Remember these key points:
- Set bit 0 of branch register to 1 when switching from Thumb to Arm state.
- Clear bit 0 when returning from Arm back to Thumb.
- Use BLX instead of BX when calling Arm functions to automatically save return address.
- Employ interworking veneers to transition between Arm and Thumb states.
Using BX and BLX correctly ensures that program execution flows smoothly between Arm and Thumb state. Avoid common errors like failing to set bit 0 when necessary or corrupting link registers. With the guidelines provided here, developers can efficiently interface Thumb and Arm code using BX and BLX.
The rest of the article provides more details on the technicalities of using BX and BLX, options for managing state changes, interfacing assembly with high-level languages, and more examples of common pitfalls and solutions.
Technical Details on BX and BLX
This section covers the technical details of how BX and BLX work under the hood…
The BX instruction branches to the address specified in the operand register, and optionally changes from Arm to Thumb state or vice-versa based on bit 0 of the register. The syntax is: BX Rn
BX does not affect the condition flags. The PC will be aligned correctly for the switched-to state after branching.
The BLX instruction branches to the address in the operand register, sets the return address in LR, and optionally changes state. The syntax is: BLX Rn
BLX sets bit 0 of LR to 1 if switching to Arm state, or 0 if staying in Thumb state. This ensures the return occurs to the correct state later. The CPSR is also updated to reflect the new state.
Operation
In pseudocode, the BX operation looks like: if Rn[0] == 1 Switch to ARM state Branch to Rn[31:1] else Stay in THUMB state Branch to Rn[31:1]
And the BLX operation: if Rn[0] == 1 LR = return address with bit 0 = 1 CPSR.T = 0 // Switch to ARM state Branch to Rn[31:1] else LR = return address with bit 0 = 0 Stay in THUMB state Branch to Rn[31:1]
The conditional flags are unaffected in both cases…
Managing State Transitions
There are some key considerations when managing the transition between Arm and Thumb states in a program:
Entry Point
The initial entry point of a program determines the initial state. For Cortex-M processors this is always Thumb.
BLX Usage
Using BLX to call Arm functions from Thumb code automatically handles setting LR for return. Don’t forget to clear bit 0 of LR in the Arm code before returning.
BX Usage
Take care when using BX to return to Thumb from Arm, as LR will not have been set up. You need to manually clear bit 0 in this case.
Veneers
Veneers provide a simple way to transition between states from any code section, avoiding limitations with BX and BLX. Useful when you need to call between Thumb and Arm freely.
LR Management
Ensure LR is always set correctly for each state transition, whether by BLX on calls or manually clearing bit 0 on returns.
Nested State Changes
Take extra care with nested state changes, as multiple transitions can cause confusion. Proper LR management is critical.
Interfacing Assembly with High-Level Code
When interfacing hand-written Arm/Thumb assembly with high-level C/C++ code, there are some additional considerations…
Function Attributes
Use function attributes like __thumb__ or __arm__ to indicate the expected state: void __thumb__ foo() { // Thumb code } void __arm__ bar() { // Arm code }
LR Management
The compiler will handle LR setup on calls between high-level functions. But pay close attention to LR when branching between assembly and high-level code.
Veneers
Veneers can be useful for interfacing assembly functions in one state with high-level code in another state: __asm__(“.thumb\n” “.thumb_func\n” “veneer:”); __asm__(“.arm\n” “arm_func:”);
Assembly Directives
Use directives like .ARM, .THUMB, etc. to switch assembly state where needed.
Common Pitfalls
Some common pitfalls when using BX, BLX, and managing state transitions:
- Forgetting to set bit 0 of branch register when switching to Arm state.
- Failing to clear bit 0 when returning to Thumb code.
- Neglecting to maintain LR correctly across state transitions.
- Using BX or BLX inappropriately within a Thumb function.
- Branching directly to an Arm address from Thumb without a veneer.
- Incorrectly handling nested state switches leading to confusion.
- Not using appropriate directives or attributes when interfacing assembly with high-level languages.
Keep these common errors in mind, and follow the best practices outlined earlier to avoid such pitfalls.
Debugging Techniques
Here are some tips for debugging errors related to incorrect use of BX or BLX…
- Step through code carefully in assembly mode to follow state changes.
- Check branch addresses to confirm destination and originating states.
- Examine LR values after calls and returns to verify correct bit 0 usage.
- Insert exception handlers and breakpoints to identify illegal execution flows.
- Trace function calls and returns – mismatched states indicate a problem.
- Add ASSERT macros to validate assumptions and catch errors early.
Proper debugging techniques allows problems to be identified even in complex interworking code. Take the time to carefully follow the code flow and verify that states are switched correctly.
Mixed Language Programming
Building applications using both Thumb, Arm, and high-level languages together requires careful management of the interacting code:
- Use veneers whenever possible as they simplify interoperation.
- Clearly define and document interfaces between language boundaries.
- Employ clear coding guidelines for each language section.
- Extensively test interactions between components in different languages.
- Use linker scripts and directives to control memory placement.
- Define rigorous change management processes to avoid introducing defects.
With proper architecture and planning, components in different languages can work together smoothly. Validate all interactions through testing.
Design Considerations
Here are some design factors to consider when developing with BX/BLX instructions:
- Minimize state transitions to simplify design.
- Isolate and encapsulate Thumb and Arm sections into library functions where possible.
- Use macros or functions for common interworking code sequences.
- Design flexible interfaces using directives or attributes to support multiple states.
- Consider performance tradeoffs between Thumb, Arm, and high-level code.
- Document the expected state for every interface point in the program.
Following arm compiler recommendations and best practices will lead to robust interworking code. Validate with extensive testing.
Performance Considerations
The performance difference between Thumb, Arm and high-level code is an important consideration:
- Thumb code is more compact but lower performance than Arm.
- Frequently executed code should usually be Arm for best performance.
- Thumb exception handlers reduce size and improve interrupt latency.
- Compute-intensive sections are often best implemented in Arm code.
- High-level C/C++ is easiest for complex logic but incurs overhead.
- Profile aggressively and focus optimization effort appropriately.
Understand the tradeoffs and locate performance hotspots to determine optimal implementation language.
Conclusion
BX and BLX instructions enable switching between Arm and Thumb states, but using them correctly is vital. Ensure bit 0 is set properly on registers to trigger state changes where required. Manage link registers carefully across transitions. Employ veneeers and directives to simplify complex interworking code flows. With a solid understanding of the concepts presented here, developers can utilize BX and BLX effectively to build robust mixed-state applications.