3
votes

community!

I have the following one-line source file, called first.S

mov R1, R2

I generate an object file as follows:

$ arm-none-eabi-as -mcpu=cortex-m3 -march=armv7 -mthumb -c -o first.o first.S 

Then, I disassemble it.

$ arm-none-eabi-objdump -d first.o

first.o:     file format elf32-littlearm


Disassembly of section .text:

00000000 <.text>:
   0:   1c11        adds    r1, r2, #0

Obviously, the two instructions (mov and add) in this case have the same, desired, effect.

The question, though, is: why?

According to the ARMv7-M architecture reference manual, there exist several encodings for mov register instructions, however the assembler chooses to encode it as an add instruction.

Is there a place in any documentation that would describe such a decision?

Thanks!

2
The instruction set reference describes what instruction should be used. It changed from adds Rd, Rn, #0 to lsls Rd, Rn, #0 in ARMv6 and now there's a truly not flag-setting mov Rd, Rn using an extended form of the previous Hi/Lo register move instruction.fuz
@fuz I managed to find a reference to the change from mov to add in the Thumb2 supplement of the ARM Architecture Reference Manual from 2005. ThanksAlexandru N. Onea

2 Answers

5
votes

From the beginning of time for the thumb instruction set, the encoding

0001110xxxnnnddd 

is an

adds rd,rn,#xxx

it is the more efficient encoding IF modification of the flags is okay for you.

The pseudo instruction mov rd,rn means the flags can change (both per arms asm in the document and gas apparently). So that original encoding is fine.

Now it is up to the disassembler to choose to have an if immed == 0 then print mov rd,rn vs adds rd,rn,#0, both are correct disassemblies.

Now the mov with one register high and one low says:

Unlike the low register MOV instruction described in MOV (2) on page A7-73, this instruction does not change the flags.

And now it gets into the assembly language which is wholly defined by the assembler not the target (NOT ARM), and the horrible unified syntax and on and on. So now it becomes a tool specific thing. Gnu assembler for thumb does not like adds for example (non unified syntax, which I find significantly easier to use for thumb) you do an add and get an adds.

.thumb
add r0,r1,#0
mov r0,r1
adds r0,r1,#0
movs r0,r1

arm-none-eabi-as so.s -o so.o
so.s: Assembler messages:
so.s:5: Error: instruction not supported in Thumb16 mode -- `adds r0,r1,#0'

.thumb
add r0,r1,#0
mov r0,r1
movs r0,r1

00000000 <.text>:
   0:   1c08        adds    r0, r1, #0
   2:   1c08        adds    r0, r1, #0
   4:   1c08        adds    r0, r1, #0

Yet it is fine with movs.

0x1c08 = 0x0001110000001000 and that is an adds thumb instruction going back to armv4t when this all started.

.syntax unified
.thumb
add r0,r1,#0
mov r0,r1
adds r0,r1,#0
movs r0,r1


   0:   f101 0000   add.w   r0, r1, #0
   4:   4608        mov r0, r1
   6:   1c08        adds    r0, r1, #0
   8:   0008        movs    r0, r1

So now in this case, it is a different assembly language (same tool different assembly language)

So this assembly language honors the add vs adds and mov vs movs.

In order to do an add without the flahs you need the thumb2 encoding. The mov without the flags is the high register mov 0x4608 0100011000001000 0x46xx

adds is as it always was and movs has now been encoded as a shift left, but instead of disassembling lsl r0,r1,#0 they instead disassemble as mov r0,r1, more for you to chew on instead of just the mov disassembling as an add. Why didn't they use adds? And here is another problem with this, if you look at the mov low registers instruction at least in the old arm arm, it describes what happens to the flags it shows the adds encoding. But if you look at the lsl description the flags are different, lsl is not a replacement for a mov with flags at least as described in the longest living ARM ARM (with thumb).

Okay and that makes sense, they were being helpful in the older arm arm. if the immediate is zero then there is no carry out so it is described as being set to zero along with the signed overflow flag.

Lsl shows carry as unchanged rather than zero in one document vs another. So perhaps some changes happened in the implementation of the instructions over time or one of the ARM ARMS is wrong (that happens often).


Short answer, mov rd,rn has always been a pseudo instruction documented as an adds, the disassembler can choose to print it out either way it is up to the disassembler.

The assembly language is defined by the tool not the target, so the tool determines which flag solution to use within its syntax and can pick between the adds, the mov high registers, a thumb2 encoding or some other encoding.

We won't know, the why question, why pick one encoding over another where equal encodings are possible, often the shorter one (thumb vs thumb2 extension) are often chosen (xor in x86 vs a mov immediate with zero as the immediate). But lsl vs add vs sub vs ...

There are other pseudo instructions that you will find either in the arm documentation (documents the assembly language of its tool at that time), as well as pseudo instructions that the assembler adds to its assembly language like nop.

.thumb
nop
mov r8,r8
mov r4,r4

00000000 <.text>:
   0:   46c0        nop         ; (mov r8, r8)
   2:   46c0        nop         ; (mov r8, r8)
   4:   1c24        adds    r4, r4, #0

And now the question is why didnt they just print this out:

   0:   1c08        mov r0,r1  ; (adds r0, r1, #0)

I also love how the disassembler implies a semi colon as a comment boundary where the assembly language strangely does not support that (like every other assembler on the planet (well a majority)).

Granted the disassembler doesn't know what the assembler was that created that machine code, so for cases where there is a pseudo instruction showing both would be nice.

0
votes

As in https://static.docs.arm.com/ddi0403/eb/DDI0403E_B_armv7m_arm.pdf#G11.5007716 , the move instruction's opcode is also 2bytes and therefore there is no advantage in using either of those. https://developer.arm.com/documentation/ddi0337/e/instruction-timing/processor-instruction-timings states, that both instructions use 1 CPU-cycle to complete.

Encoding T2 https://static.docs.arm.com/ddi0403/eb/DDI0403E_B_armv7m_arm.pdf#G11.5007716 however would have a null byte which is bad in some scenarios (exploits).

However I can imagine building an assembler being more easy the less different opcodes it will produce.