Support for LITBASE
DanielAW opened this issue · 2 comments
Hi,
I have an Xtensa binary which uses LITBASE (special register #5). Without honoring it l32r instructions will always point to the wrong target address. Here is a relevant section from the reference manual:
I would like to help implementing LITBASE into this project, where could I begin? For starters, LITBASE could just be hard coded.
Hi, thanks for the message!
The answer depends on how the firmware is using the LITBASE register.
- If it's always a constant, we could just expose a load option to set the value
- If it's only set in one place throughout the entire binary, we can leverage Binary Ninja's global register support.
- If it's always set right before a series of L32R instructions we could lift it as another register and let dataflow compute addresses. Of course, this would clutter the output if it's ever not set, as we'd effectively lose reads from globals.
- There's always the hack of lifting it as part of the L32R instruction in the lifter (you don't need a one-to-one relationship between instructions and lifting results). However, to do that you need the WSR.LITBASE instruction and the L32R to be very close together, and I'm guessing that's not the case.
- If it crosses function boundaries and changes often that could be obnoxious, I'm hoping that's not the case :)
- There appear to be shenanigans you can pull with the calling convention if it comes down to it.
- The ISA reference indicates this is unlikely, that it typically doesn't change across function calls.
We may need to expose a load option so as not to affect decompilation on chips that don't use it. This plugin best supports the ESP8266, and it works fairly well, so I'd like to preserve that, but I'd also like to support whatever chip this is! I'm open to merging a hack here if that's easiest to implement. However, if it would break non-LITBASE cases, I'd like to put it behind a load option.
It'd be useful to have a sample binary for documentation & testing purposes. I'm guessing you probably can't share the firmware from the device under analysis. Do any of the free SDKs use this architecture option? I just checked and the ESP32 does not, and I'm pretty sure the ESP8266 doesn't either.
If there's no freely distributable example, can you share a snippet of disassembly that demonstrates how the firmware is using the LITBASE register?
Do any of those options sound like something you'd like to implement? Your best bet is probably 1) or 2)
Hi Zack,
thanks for your answer!
The LITBASE registers is set only once in my binary:
0097f0ae <unpackdone>:
97f0ae: ffd921 l32r a2, 97f014 <_ResetVector+0x14>
97f0b1: 130520 wsr.litbase a2
97f0b4: 002010 rsync
I patched instruction.py
to use a fixed LITBASE value, that worked already!
diff --git a/binja_xtensa/instruction.py b/binja_xtensa/instruction.py
index 7243f07..e7f1700 100644
--- a/binja_xtensa/instruction.py
+++ b/binja_xtensa/instruction.py
@@ -34,6 +34,8 @@ Link to the Xtensa docs/manual I was referencing:
"""
from enum import Enum
+LITBASE = 0x408000 + 0x40001
+
# https://stackoverflow.com/a/32031543
def sign_extend(value, bits):
@@ -233,7 +235,8 @@ class Instruction:
def offset_l32r(self, addr):
enc = sign_extend(self.imm16 | 0xFFFF0000, 32) << 2
- return (enc + addr + 3) & 0xFFFFFFFC
+ return (LITBASE & 0xFFFFF000) + enc
+ #return (enc + addr + 3) & 0xFFFFFFFC
# mem_offset is roughly the same as target_offset, but for data accesses and
# not jumps
But of course this brakes the original functionality of your plugin. Where would be a good point to set a "global register"? I guess during parsing of the WSR instruction.
I would not like to share the binary publicly, if you can share you mail address I can send it to you.
Thanks,
Daniel