实现目标
在 CW 平台,编写一段能在 RAM 中运行的代码,并且代码的定位是自由的。
硬件平台:MC9S12HY64
软件平台:CodeWarrior V5.0
实现方法
要实现与代码定位无关的代码需要设置 CW 的编译选项中的相关项,开启方法:
打开 CW 工程,勾选:Edit->Standard Settings->Compiler for HC12->Option->Code Generation->Generate Position Independent Code
生成代码存在的问题
1. 使用的变量地址是固定的,如果直接移植代码,变量地址访问出错。所以变量地址不能改变。
2. 通过给函数传递入口参数,如果直接变量传递,在变量不多的情况下,可以通过编写汇编代码将参数和 堆栈连接实现,如果对于汇编不熟悉的情况(我就是这种情况),会比较困难。如果将多个变量打包成结构体变 量,然后给函数代码传递结构体指针,参数虽然只有 1 个,但是函数的代码都将是指针操作,可能会出现代码量 增大或者运行速度降低等问题。
经过摸索,找到一个可以实现变量和代码定位在任意位置的办法。 代码中的对外入口直接使用全局变量,当然,如果变量地址变化,也会产生变量访问错误的问题,解决的办
法是,先编译调试好代码,然后反汇编,将代码数据做成一个一维数组,然后将代码中的全局变量信息(全局变 量地址和变量偏移量)和对应汇编代码地址偏移量提取出来,将 2 个数据做成一个结构体,然后提取汇编代码中所有地址数据和对应的索引偏移,做成一个结构体数组,这样一来,在加载代码到 RAM 的时候,只需要修改对 应索引偏移所在的地址数据,即可达到代码和变量的重新任意定位的效果。提示一点,可以将代码中的所有访问 的全局变量做成一个结构体,那么在进行重新定位的时候,可以通过循环体的方式来实现,这样效率高,而且方 便。例子如下:
typedef struct PG_INF
{
UINT8 CommandType; //变量首地址偏移量 = 0
UINT8 BlockIndex; //变量首地址偏移量 = 1
UINT16 DestAddr; //变量首地址偏移量 =2
UINT16 DataSize; //变量首地址偏移量 =4
UINT8 Pg_State; //变量首地址偏移量 = 6
UINT8 Buf[512]; //变量首地址偏移量 = 7
UINT8 CheckSum; //变量首地址偏移量 = 519
};
typedef struct // 变量地址重定位
{
UINT16 AsmCode_Index; // 汇编代码偏移索引号
UINT16 GVal_Offset; // 变量首地址偏移量,不同的值代表了结构变量体 Pg_Frame 的不同变量
}GVal_Ram_Locate;
const UINT8 Pg_Code[] = // 代码表
{
/* Flash_CommandProcess 函数代码 */
0x1B,0xF1,0xEC,0xCE,0x00,0x00,0xC6,0x01,0x7B,0x00,0x06,0x69,0x85,0xC7,0x87,
0x6C,0x80,0xE6,0x30,0xEB,0x85,0x6B,0x85,0xED,0x80,0x02,0x6D,0x80,0x8D,0x02,
0x07,0x25,0xF0,0xF6,0x02,0x07, …………
};
const GVal_Ram_Locate Gval_Ram_LocateTbl[] = //地址定位表
{
{ 0x0004, 0x0000 },
{ 0x0009, 0x0006 },
{ 0x0022, 0x0207 }, …………
};
这里举的例子,全局变量首地址是 0x0000
图中 2 号框
0003 ce0000 [2] LDX #Pg_Frame
最前面的其中 0003 是 ce0000 汇编代码偏移地址。ce 是汇编指令,ce 后面的 0000 操作数,这里他的值就是变量 地址+变量首地址偏移量,它占据了汇编代码偏移地址的 0004-0005 两个字节。所以汇编代码偏移索引号 Gval_Ram_LocateTbl[0]. AsmCode_Index = 0x0004 。
这里#Pg_Frame 就是直接取全局变量 Pg_Frame 的首地址 = 0x0000,所以这里的变量首地址偏移量=0x0000,即 Gval_Ram_LocateTbl[0]. GVal_Offset = 0x0000,它对应了 Pg_Frame. CommandType 变量。 假如全局变量的首地址是 0x3000,则修改为
0003 ce3000 [2] LDX #Pg_Frame
所以可以很清楚的看到,通过汇编代码偏移索引号(Gval_Ram_LocateTbl[]. AsmCode_Index)就可以定位到需要
修改的位置,然后将对应位置的值改成全局变量首地址 + 变量首地址偏移量(Gval_Ram_LocateTbl[].GVal_Offset),就可以对函数代码和变量进行任意的重新定位。前提条件就是要确定变量的地址,我想这一点是
很容易做到的。
同理:图中 3 号框的
0008 7b0000 [3] STAB Pg_Frame:6
(这里的汇编代码是编译后没有连接定位的代码,所以 0000 是没有计算变量首地址 + 变量首地址偏移量的,假
如变量首地址=0x3000, 变量偏移量=6,连接定位后的目标代码应该是 7b3006 ) Gval_Ram_LocateTbl[1]. AsmCode_Index = 0x0009, 汇编代码的偏移地址=0x0009, Gval_Ram_LocateTbl[1]. GVal_Offset = 0x0006, 变量首地址偏移量= 0x0006,即 Pg_Frame. Pg_State
图中 4 号框的
0021 f60000 [3] LDAB Pg_Frame:519
Gval_Ram_LocateTbl[2]. AsmCode_Index = 0x0022, 汇编代码的偏移地址=0x0022,
Gval_Ram_LocateTbl[2]. GVal_Offset = 0x0207,变量首地址偏移量= 0x0207,即 Pg_Frame. CheckSum
代码中的变量重新定位,示例代码
UINT16 i ;
UINT16 code_addr = 0x5000; //准备将代码拷贝到 RAM 区的 5000 地址处
volatile struct PG_INF Pg_Frame @0x3900; //函数代码中需要访问的结构体变量,变量地址定位在 0x3900
for( i=0; i<sizeof(Gval_Ram_LocateTbl) / sizeof(GVal_Ram_Locate); i++ ) // 对汇编代码进行重定位
{
*(UINT16*)( code_addr + Gval_Ram_LocateTbl[i].AsmCode_Index ) = Gval_Ram_LocateTbl[i].GVal_Offset + (UINT16)&Pg_Frame;
}
这里唯一繁琐的过程是制作 Gval_Ram_LocateTbl 表,因为要对照着汇编代码进行一个一个将变量地址信息 提取出来。还有就是如果要修改代码,则要重新做一次表,比较繁琐,不过,将代码拷贝到 RAM 中来运行,一 般的情况下来说,代码量不会很大,修改的可能性也比较小。
在 RAM 中运行代码,可以应用于 Bootloader、Flash 编程、要求提高运行速度等方面。
本人水平有限,难免有不当之处,希望能指出,大家一起讨论!