在指令中间跳转 Coldfire 固件

逆向工程 拆卸 嵌入式 摩托罗拉
2021-07-02 06:27:01


我正在研究嵌入式系统 Coldfire ROM。目前,我正在尝试对其进行逆向工程,以获得有关其结构的更深入的知识。
ROM 代码质量似乎很低,我看到很多冗余和死代码,好像代码是用非常低的优化级别编译的。
我之所以告诉你,是因为我不明白这个事实是否与我即将提出的问题有某种关系。
经常检查代码,我进入了单个多字节指令内的跳转。我是这项技术的新手,所以我想知道是否有人可以让我正确理解这一点,我认为这是一个奇怪的问题。
这是我所指的一个例子:我有一个从ROM开始的函数

[...]
.ROM:00005490 30 07             movew %d7,%d0
.ROM:00005492 4a 80             tstl %d0
.ROM:00005494 66 0c             bnes 0x0000000c :12 
.ROM:00005496 70 00             moveq #0,%d0
.ROM:00005498 60 00 00 a8       braw 0x000000b2
.ROM:0000549c 4e b9 00 00 36 f8 jsr 0x000036f8  : the jump points inside here
.ROM:000054a2 32 06             movew %d6,%d1
.ROM:000054a4 48 c1             extl %d1
.ROM:000054a6 20 01             movel %d1,%d0
[...]

在 ROM 的另一个区域,我有另一个函数,它从ROM:00012016开始

[...]
.ROM:000120c0 72 00             moveq #0,%d1
.ROM:000120c2 12 00             moveb %d0,%d1
.ROM:000120c4 20 3c 00 00 00 ff movel #255,%d0
.ROM:000120ca b2 80             cmpl %d0,%d1
.ROM:000120cc 66 00 01 92       bnew 0x00012260
.ROM:000120d0 4e b9 00 00 54 9e jsr 0x0000549e :here the jump I do not understand
.ROM:000120d6 72 00             moveq #0,%d1
.ROM:000120d8 12 00             moveb %d0,%d1
.ROM:000120da 20 3c 00 00 00 ff movel #255,%d0
[...]

如果我尝试跟随跳转并从地址ROM:0000549e开始反汇编函数,我会得到一个翻译,导致以下解释。我知道它是可执行的,但我没有得到这个动作的大图。

[...]
.ROM:0000549e 00 00 36 f8                      orib #-8,%d0
.ROM:000054a2 32 06                            movew %d6,%d1
.ROM:000054a4 48 c1                            extl %d1
.ROM:000054a6 20 01                            movel %d1,%d0
[...]

在这项技术的背后,是否有一些我应该知道的旧做法。为什么这个ROM开发者应该使用这种奇怪的技术?减少代码大小?如果是这样,它就不适合其余的代码,这些代码非常冗余,并且其中包含永远不会执行的死代码!


编辑.20190214
在字节 0xc000 处,它开始一段代码扩展到 0x40000,其第一个函数在其开始部分似乎不完整。
怪异似乎从这一点开始,并延伸到固件第二部分开始的字节 0x40000。
在这个代码块中,大约有 200 个不同维度的函数,并且这个块中的所有函数中都没有出现具有奇怪绝对地址JSR
此块中的功能似乎相互无缝交互,但它们偶尔会出现地址不一致的情况。

3个回答

它看起来确实像一些微控制器中采用的尺寸优化,尽管我还没有看到它用于子程序调用。特别是 HC08 和 HC12 的 CodeWarrior 编译器使用它来优化短分支。

来自飞思卡尔/恩智浦的S12(X) 构建工具参考手册HC(S)12 后端优化部分):


短文胸优化(-OnB=a 禁用它)

一个字节上的分支被替换为操作码BRN两个字节上的分支被替换为代码CPS 清单 10.19 短 BRA 优化示例的操作码

int q(void) {
if (f()) {
    return 1;
  } else {
    return 0;
  }
}

使用此优化生成的代码:

0000 160000 JSR f
0003 044403 TBEQ D,3 ;abs = 0009
0006 C601 LDAB #1
0008 21C7 BRN -57 ;abs = FFD1
000A 87 CLRA
000B 3D RTS

使用-OnB=a(禁用短 BRA 优化)选项,编译器会产生一个多字节:

0000 160000 JSR f
0003 044404 TBEQ D,4 ;abs = 000A
0006 C601 LDAB #1
0008 2001 BRA 1 ;abs = 000B
000A C7 CLRB
000B 87 CLRA
000C 3D RTS

分支优化器将第二个示例中的 BRA 1 替换为操作码“BRN”,0x21。然后解码器将 BRN 和 CLRB 加入一个 BRN。实际上,解码器会写如下内容:

0008 21 “BRA 1”
000A C7 CLRB

CLRB第二个代码out 在第一个列表中消失在BRN指令的偏移量中 同样类型的优化也使用BRA 2. 然后CPS #a 的操作码NOTEBRNCPS在解码器列表中通常是这种优化的结果。如果是这样,则在操作码之后隐藏一两个额外的机器指令。编译器将此作为SKIP1SKIP2伪操作码写入列表文件。


我还没有发现提及用于 ColdFire 的此类优化,并且由于您提到代码看起来没有优化,因此可能不是这里发生的事情。我的一个理论是,重叠指令序列来自编译器的标准库而不是开发人员的代码,因此它已针对大小进行了优化,甚至在汇编中手动编写。

另一种选择是您正在查看被反汇编的数据,这可能会产生各种奇怪的效果。例如,它可以是嵌入在代码中的跳转表。

编辑是的,000120c0 处的东西确实看起来像某种表格。例如,当转换为一个单词数组时,它变成:

CODE:000120C0  dc.w $7200, $1200, $203C, 0, $FF, $B280, $6600, $192, $4EB9
CODE:000120C0  dc.w 0, $549E, $7200, $1200, $203C, 0, $FF

所以它可能根本就不是代码。我怀疑它可能是初始任务表或类似的。

现在您已经向我们展示了更多的代码,一种模式开始出现。

jsr     unk_A75E       ; This function does not exist.
...
jsr     (loc_576A+2).l  ; ...points in the middle of an instruction
...
jsr     (loc_88C+4).l   ; ...points in the middle of an instruction

有时会使用跳转到指令的中间来生成稍微更快和更紧凑的代码(例如,在加载具有两个可能值之一的寄存器时消除分支)。

然而,在这个例程中,每条jsr 指令似乎都指向无效代码,没有明显的原因。地址是绝对立即数,并且您的 CPU 没有 MMU,因此这些地址必须有效。但是,这些位置的代码可能不是您认为的那样。

您可能假设 ROM 从地址 0 开始。如果不是这种情况,那么与 pc 相关的代码(分支指令等)仍将指向“正确”地址,但绝对跳转不会。另一种可能性是 ROM在加电时确实从地址 0 开始,但后来被 RAM 交换和/或被特殊硬件移动到更高的地址。代码也可以从 ROM 复制到 RAM(可能在不同的地址)以供执行。这样做有几个可能的原因:-

  1. RAM 的循环时间可能比 ROM 短,因此复制到 RAM 的代码执行得更快,需要更少(或不需要)等待状态。

  2. ROM 需要在启动时位于位置 0,因为代码执行从位置 0 开始。然而,中断向量也出现在低内存区域,如果它们在 ROM 中,则它们在运行时无法更改。

  3. 绝对短寻址(只能访问内存的第一个和最后一个 32k)比长寻址快一点,因此可能需要在这个区域有 RAM 来存储经常访问的变量。

您应该分析从位置 0 开始的代码,验证它是否执行了必要的操作,例如加载堆栈指针、清除 RAM 和初始化 I/O 硬件。然后寻找任何将可执行代码从 ROM 复制到 RAM 的东西,以及可能改变内存映射的 I/O 操作。从中您应该能够弄清楚那些 jsr 指令所指向的位置的真正含义

您没有确切说明此代码适用于哪个 CPU,坦率地说,我无法费心浏览所有 Coldfire V2 数据表以找出它们可以做什么。但是您应该这样做,因为它可能具有与您的问题相关的功能。为了充分理解,您还应该跟踪电路以确定 I/O 引脚的位置和功能等。逆向工程时,每一点信息都有帮助!

这些现象可以通过用于在闪存上编写代码的技术来解释。
代码由两部分构成的事实将允许开发人员在两个不同的时刻在闪存中编写代码。
根据 MCF5282 和 MCF5216 ColdFire 微控制器用户手册,修订版 3第 6-19 页指出:
因此,单个擦除页实际上是 2 KB
(0xc00=24 页)。
带有奇怪的 JSR 目标的代码很可能是从以前版本的垃圾中派生出来的,而不是通过在较低内存中写入当前代码的过程擦除的。


IgorSkochinskyBruceAbbott的帮助下详细阐述了这个答案