binutils-esp32ulp
binutils-esp32ulp copied to clipboard
Different JUMP with global / not-global symbol
Overview
For JUMP instructions using an absolute/constant symbol as an argument, binutils-esp32ulp produces a different output depending on whether that symbol is exported with .global
or not. This appears to be a bug.
The following example code can be used to reproduce the issue:
.set const, 12
.global const
entry:
jump const
Assemble and link the example as follows:
base=global_bug # given the source is called "global_bug.s"
esp32ulp-elf-as -o ${base}.o ${base}.s
esp32ulp-elf-ld -T esp32.ulp.ld -o ${base}.elf ${base}.o
esp32ulp-elf-objcopy -O binary ${base}.elf ${base}.bin
Inspect the binary output as follows:
xxd global_bug.bin
Current behaviour
When building the example code as is, the binary result is:
0000000: 756c 7000 0c00 0400 0000 0000 3000 0080 ulp.........0...
However, when commenting out (or removing) the line .global const
- in other words const
is NOT exported, then the binary result becomes:
0000000: 756c 7000 0c00 0400 0000 0000 0c00 0080 ulp.............
Notice the last (JUMP) instruction is different: 3000 0080
vs 0c00 0080
.
Decoding those two, I get
# global
3000 0080:
dreg = 0 # 0
addr = 12 # Target PC
unused = 0 # Unused
reg = 0 # Immediate mode
type = 0 # BX_JUMP_TYPE_DIRECT
opcode = 8 # OPCODE_BRANCH
sub_opcode = 0 # SUB_OPCODE_BX
# NOT global
0c00 0080:
dreg = 0 # 0
addr = 3 # Target PC
unused = 0 # Unused
reg = 0 # Immediate mode
type = 0 # BX_JUMP_TYPE_DIRECT
opcode = 8 # OPCODE_BRANCH
sub_opcode = 0 # SUB_OPCODE_BX
Notice how the addr
field is different. In fact in the second case (not global) the symbol value has been divided by 4.
I assume this is related to address translation from bytes to words, however the behaviour should at least be consistent, no matter whether a symbol is marked global or not.
Expected behaviour
I would expect the correct behaviour to be, that the JUMP instruction uses the symbol value as is (ie. addr == 12
- not divided by 4).
This would match how the ESP32 ULP coprocessor instruction set documentation describes that arguments, which are constants rather than labels, are used without conversion.
Furthermore, the JUMPR
and JUMPS
instructions behave that way too (i.e. they use those symbol values without conversion), irrespective of whether the symbol is marked global or not.
One small extra detail - about where the decision is made for what addr
should be.
If you look at the content of the .o object file (output of as
), you will see that:
- when the symbol is NOT global, the
addr
value in the instruction is already set to something at that stage. - However, when the symbol is marked global,
as
setsaddr
to 0 and it's only during linking, thatld
fills in the final value into the instruction.
So as
and ld
appear to handle this case differently and shouldn't.