如何用Keil ARM编译器作x65的Patch
主要以"替换运营商标志为自定义字串V2"为例说一下怎么用Keil ARM编译器来做Patch或者移植。最近一段时间,因为Patch的原因,我试用了所有的主流ARM编译器,包括ADS1.2,RealView2.0,GCC for ARM,Keil ARM。最后发现,最合适的工具就是Keil ARM。虽然还有一些小问题,不过起来最解决。首先,Keil在代码定位方面是最容易的,ADS和GCC都需要用独立的链接文件才能做到Patch这样的松散定位,但仍然很费劲,而且比如ADS需要你的段开始于4字节的边界。这是很麻烦的,很多时候我们要Hook的BL指令开始于2字节边界,此外,这些编译器对于调用地址有很多限制,比如简单的BL指令,在ADS中就很难完成,而BLX指令在ADS中甚至会出错。
痛苦中,我甚至打算自己写一个汇编器,实际我也完成了词法和语法,可以处理简单的BL和BLX,不过从avkiev那里得到解决BLX的办法后,我还是放弃了这个计划。虽然Keil有一些缺陷,但毕竟可以忍受。为了配合Keil,我又重新写了一下Hex2VKP工具,目的只有一个,使Patch工作变简单,目标就是像Riza的sfe那样。(我的编译器计划是apm,ARM Patch Maker)。
下面我就结合"替换运营商标志为自定义字串V2"说明一下基本步骤,为了这个,我已经把它完全移植到Keil下了。下面是这个Patch的所有工程文件。
这个工程里面主要有三个源文件,startup.s、RepSP.c、x65.h。还有Keil的工程文件RepSP.Uv2和Hex2vkp这个工具。startup.s是汇编文件,主要是Hook部分及一些Thunk的函数。RepSP.c是主文件,里面包括了所有的程序部分。而x65.h是头文件,里面定义了一些函数原形和结构体。hex2vkp是我专门为hex文件转换为vkp而写的工具,最新重写了一下,增强一下功能,它的格式如下:
hex2vkp [-h? -bbaseaddr -fflashfile -ccommentsfile -ovkpfile]
-cspecify a commentsfile to describe vkp, all line should be output
until a line begin with ';;' is found.
-bspecify base address of fullflash, default A0000000
-fspecify original fullflash file
-hproduce this help message
-ospecify output vkp file, default to console
-vreport hex2vkp version info
可以指定基地址,默认是A0000000,fullflash文件,注释文件和输出文件。同时输入可以指定一系列的hex文件。
我们主要看一下这个工程中和地址定位相关的部分。
首先是整个工程的地址,在这里面我使用了0x800000开始的Flash部分,指定是在工程的目标选项的的LA Location的里面,作如下指定。
User Classes:DATA (0xA8000000-0xA87FFFFF), CODE (0xA0800040-0xA081FFFF), CONST (0xA0800040-0xA081FFFF)
这里面主要指定了程序的数据部分、代码部分、常量区。
然后是Hook的地址指定,这个我们看一下startup.s。
;***替换运营商标志为自定义字串V2.1***
;(C) Bennie
;for S6CV50
;v2.1->重写了代码
;支持的格式:
;%Y- 年(四位数字)2005
;%y- 年(两位数字)05
;%M- 月06
;%D- 日23
;%W- 星期 四
;%H- 时 00
;%F- 分 56
;%R- 信号强度 -46
;%A- 电池电压 3807
;%a- 电池电压,百分数 56
;%T- 环境温度 30,74
;对齐格式字符:0xE01C=中,0xE01D=右,0xE01E=左
;屏幕保护自定义字串,注意必须以\0结尾,最大15个字符。
;0x800000: '%M-%D %W %H:%F\0'
;待机画面自定义字串
;0x800020: '%R %A %T\0'
;;==============================================================
EXTERN CODE16(entryMainscreen?T)
EXTERN CODE16(entryScreensaver?T)
;待机时调用获得运营商名字的函数Hook
AREA STARTUPCODE1, CODE, AT 0xA0AFCB82 // READONLY, ALIGN=4
CODE16
PUSH {LR}
BL entryScreensaver?T
POP {PC}
;屏保时调用获得运营商名字的函数Hook
AREA STARTUPCODE2, CODE, AT 0xA08DAF2E // READONLY, ALIGN=4
CODE16
BL entryMainscreen?T
;函数Thunk,处理一下dwmoddw函数
AREA THUNKFUNCTION, CODE, AT 0xA0800040 // READONLY, ALIGN=4
PUBLIC dwmoddw?T
dwmoddw?T PROC CODE16
LDR R2, =0xA160948C
BX R2
ENDP
END
前面的部分是最终vkp的注释部分,这个由hex2vkp输出到vkp的开始。然后前面两个Extern知名了这两个函数是外部函数,然后第一个AREA开始的部分指定了一个区段,后面是这个区段的属性,比如属于那个段(代码段)、对齐边界(默认是4),以及开始地址。可以指定地址是我选择Keil的主要原因,在别的工具上,做这个就困难多了。然后下面的CODE16是说明下面是THUMB指令,在下面就是代码部分了。下面的部分类似,除了最后一个是函数THUNK有点不同,那个主要原因是KEIL在处理多个函数返回值时,用函数指针有点问题,所以我用了C和ASM的混合链接。前面那个PUBLIC是导出一个符号,这里面体现了KEIL对混合编译的符号处理。导出的符号要加上?T或?A。才可以被别的模块使用。后面声名了一个Thumb例程。
下面我们再看一下RepSP.c里面和地址定位有关的部分。
#include "x65.h"
#define S6CV50
/*
0x800000: '%M-%D %W %H:%F\0'
0x800020: '%R %A %T\0'
*/
const word format __at 0xA0800000;
const unsigned short w1_7[] = {
0x4E00, //一
0x4E8C, //二
0x4E09, //三
0x56DB, //四
0x4E94, //五
0x516D, //六
0x65E5 //日
};
#ifdef S6CV50
//for system function
const GETDATETIME getdate = (GETDATETIME)0xA0AA6A53;
const GETWEEK getweek = (GETWEEK)0xA082AD87;
const GetAkku getakku = (GetAkku)0xA1254830;
const f_uint2str uint2str = (f_uint2str)0xA0825687;
//const DWMODDW dwmoddw = (DWMODDW)0xA160948C; //because Keil's problem
//for system data with Absolute Location.
word Akkpct __at 0xA863FF08;
word net __at 0xA86F0BB8;
#endif
extern int_int dwmoddw(int a, int b);
。。。。。。。
无关的部分我已经省略,前面是包含头文件,之后定一个宏,在下面那个格式字串就涉及到了变量的决定定址,在Keil里面试用__at关键词。然后后面是一个常量数组,需要注意的是这些一定要声明为常量,否则会被定位到数据区去。在之后就是本工程使用的函数部分。这个函数原形的声明在x65.h里面,再后面是两个全局变量的绝对定址。分别时电池容量百分比和网络信号强度。这些是和地址有关的部分,在下面的一个声明是为了和startup.s里面联合编译使用的声明部分,Keil对这种用R0-R3返回变量的功能支持也有问题,所以我用了一个Thunk。
此外,我们要设置工程的输出文件为Intel的Hex格式,然后还要设置附加的命令,让他编译完成后自动转换Hex为VKP,那么在Outpu选项卡里面不仅要设定输出格式,还要在User Program那里写下如下的命令:
.\hex2vkp -fE:\siemens\tools\S6CV50.bin-cstartup.s -oRepSP.vkp output\RepSP.hex
意思是原始文件是E:\siemens\tools\S6CV50.bin,这个需要改成你自己的,注释来自于startup.s,输出为RepSP.vkp。输入是output\RepSP.hex。
这样在编译完成后就会自动生成vkp了。
那么,了解了这个工程的各部分以后,我们看一下为了移植这个工程,你需要处理那些部分。可能这才是大家需要的!
移植这个需要处理的函数有:
const GETDATETIME getdate = (GETDATETIME)0xA0AA6A53;
const GETWEEK getweek = (GETWEEK)0xA082AD87;
const GetAkku getakku = (GetAkku)0xA1254830;
const f_uint2str uint2str = (f_uint2str)0xA0825687;
//const DWMODDW dwmoddw = (DWMODDW)0xA160948C; //because Keil's problem
AREA THUNKFUNCTION, CODE, AT 0xA0800040 // READONLY, ALIGN=4
PUBLIC dwmoddw?T
dwmoddw?T PROC CODE16
LDR R2, =0xA160948C
BX R2
ENDP
需要注意的是,如果是Thumb模式的函数,因为是函数指针,最后一个bit为1。即在函数地址上加1。特别的是dwmoddw是在startup.s里面用thumb处理的。涉及到的全局变量有:
word Akkpct __at 0xA863FF08;
word net __at 0xA86F0BB8;涉及到的函数Hook有:
AREA STARTUPCODE1, CODE, AT 0xA0AFCB82 // READONLY, ALIGN=4
AREA STARTUPCODE2, CODE, AT 0xA08DAF2E // READONLY, ALIGN=4
还有就是Patch的占用地址了,这个在工程选项里设定。
对于这些,我们从几个方面入手:
从待机的显示和处理可以找到getdategetweek dwmoddw 以及信号强度的变量地址
从工模的ACCU菜单可以找到getakkuuint2str 和电池的百分比容量。
那么我分别给出S65的类似部分。
A1253530 ; 圹圹圹圹圹圹圹?S U B R O U T I N E 圹圹圹圹圹圹圹圹圹圹圹圹圹圹圹圹圹圹圹?
A1253530
A1253530
A1253530 wndACCUMonitor ; CODE XREF: j_wndACCUMonitorj
A1253530 ; DATA XREF: ROM:off_A0A2EBD8o
A1253530
A1253530 var_34 = -0x34
A1253530 var_30 = -0x30
A1253530 var_2C = -0x2C
A1253530
A1253530 F0 4D 2D E9 STMFD SP!, {R4-R8,R10,R11,LR}
A1253534 01 40 A0 E1 MOV R4, R1
A1253538 00 70 A0 E1 MOV R7, R0
A125353C 04 00 A0 E1 MOV R0, R4
A1253540 4C 13 9F E5 LDR R1, =asc_A1410094
A1253544 18 D0 4D E2 SUB SP, SP, #0x18
A1253548 9A 20 A0 E3 MOV R2, #0x9A
A125354C 71 D7 0E EB BL memcpy
A1253550 0C 20 A0 E3 MOV R2, #0xC
A1253554 CF 1F 8F E2 ADR R1, asc_A1253898 ; "AkkuMon (1)"
A1253558 07 00 A0 E1 MOV R0, R7
A125355C 6D D7 0E EB BL memcpy
A1253560 09 10 A0 E3 MOV R1, #9
A1253564 00 00 A0 E3 MOV R0, #0
A1253568 B0 04 00 EB BL GetAkku
A125356C 08 00 8D E5 STR R0,
A1253570 00 00 A0 E3 MOV R0, #0
A1253574 07 10 A0 E3 MOV R1, #7
A1253578 AC 04 00 EB BL GetAkku
A125357C 04 00 8D E5 STR R0,
A1253580 00 00 A0 E3 MOV R0, #0
A1253584 08 10 A0 E3 MOV R1, #8
A1253588 A8 04 00 EB BL GetAkku
A125358C 00 50 A0 E1 MOV R5, R0
A1253590 00 00 A0 E3 MOV R0, #0
A1253594 06 10 A0 E3 MOV R1, #6
A1253598 A4 04 00 EB BL GetAkku
这个地方那个可以找到GetAKKU和uint2str 和百分比容量
A12536F0 7A 00 84 E2 ADD R0, R4, #0x7A
A12536F4 07 D7 0E EB BL memcpy
A12536F8 9C 04 1F E5 LDR R0, =0xA863FF08 ;这个地址就是百分比容量
A12536FC 04 20 A0 E3 MOV R2, #4
A1253700 0C 10 8D E2 ADD R1, SP, #0x38+var_2C
A1253704 B0 00 D0 E1 LDRH R0,
A1253708 DD 47 D7 FB BLX uint2str
A125370C 04 20 A0 E3 MOV R2, #4
A1253710 0C 10 8D E2 ADD R1, SP, #0x38+var_2C
A1253714 82 00 84 E2 ADD R0, R4, #0x82
A1253718 FE D6 0E EB BL memcpy
A125371C CC 54 1F E5 LDR R5, =0xA863FF00
A1253720 18 00 95 E5 LDR R0,
A1253724 00 00 50 E3 CMP R0, #0
A1253728 38 00 00 0A BEQ loc_A1253810
A125372C B2 00 D0 E1 LDRH R0,
A1253730 04 20 A0 E3 MOV R2, #4
A1253734 0C 10 8D E2 ADD R1, SP, #0x38+var_2C
A1253738 D1 47 D7 FB BLX uint2str
从GetSPNameWrap出发,可以找到待机和屏保时的Hook地址,如下:
A08DAF28 ; 圹圹圹圹圹圹圹?S U B R O U T I N E 圹圹圹圹圹圹圹圹圹圹圹圹圹圹圹圹圹圹圹?
A08DAF28
A08DAF28
A08DAF28 GetSPNameWrap ; CODE XREF: ROM:A08DB0C4p
A08DAF28 ; ROM:A08DB176p ...
A08DAF28 70 B5 PUSH {R4-R6,LR}
A08DAF2A 05 1C ADD R5, R0, #0
A08DAF2C 00 21 MOV R1, #0
A08DAF2E 21 F2 28 FE BL GetSPName ;待机时Hook的就是这个地址。屏保时Hook的是GetSPName这个函数的开始。
A08DAF32 01 28 CMP R0, #1
A08DAF34 17 D1 BNE loc_A08DAF66
A08DAF36 28 1C ADD R0, R5, #0
A08DAF38 52 F1 0A E9 BLX sub_A0A2D150
A08DAF3C 06 1C ADD R6, R0, #0
A08DAF3E 01 24 MOV R4, #1
A08DAF40 0D E0 B loc_A08DAF5E
A08DAF42 ; 哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪?
A08DAF42
A08DAF42 loc_A08DAF42 ; CODE XREF: GetSPNameWrap+38j
A08DAF42 21 1C ADD R1, R4, #0
A08DAF44 28 1C ADD R0, R5, #0
A08DAF46 52 F1 74 EE BLX sub_A0A2DC30
A08DAF4A 0A 28 CMP R0, #0xA
A08DAF4C 04 D1 BNE loc_A08DAF58
A08DAF4E 22 1C ADD R2, R4, #0
A08DAF50 20 21 MOV R1, #0x20
A08DAF52 28 1C ADD R0, R5, #0
A08DAF54 52 F1 3E EE BLX sub_A0A2DBD4
A08DAF58
A08DAF58 loc_A08DAF58 ; CODE XREF: GetSPNameWrap+24j
A08DAF58 01 34 ADD R4, #1
A08DAF5A 24 04 LSL R4, R4, #0x10
A08DAF5C 24 0C LSR R4, R4, #0x10
A08DAF5E
A08DAF5E loc_A08DAF5E ; CODE XREF: GetSPNameWrap+18j
A08DAF5E B4 42 CMP R4, R6
A08DAF60 EF D9 BLS loc_A08DAF42
A08DAF62 01 20 MOV R0, #1
A08DAF64 70 BD POP {R4-R6,PC}
A08DAF66 ; 哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪?
A08DAF66
A08DAF66 loc_A08DAF66 ; CODE XREF: GetSPNameWrap+Cj
A08DAF66 00 20 MOV R0, #0
A08DAF68 70 BD POP {R4-R6,PC}
GetDate可以查找类似的特征码,而GetWeek也可以,dwMODdw函数就在GetWeek里面,不过可能是一个Thunk跳转。
A082AD86
A082AD86 GetWeek ; DATA XREF: sub_A0C5E764:off_A0C5E76Co
A082AD86 80 B5 PUSH {R7,LR}
A082AD88 FF F7 C3 FF BL sub_A082AD12
A082AD8C 01 1D ADD R1, R0, #4
A082AD8E 07 20 MOV R0, #7
A082AD90 0D F0 CC EE BLX j_dwMODdw_0
A082AD94 08 06 LSL R0, R1, #0x18
A082AD96 00 0E LSR R0, R0, #0x18
A082AD98 80 BD POP {R7,PC}
如下,前面的其实是一个thunk。
A0838B2C
A0838B2C ; Attributes: thunk
A0838B2C
A0838B2C j_dwMODdw_0 ; CODE XREF: sub_A082AC58+Cp
A0838B2C ; sub_A082ACEC+Ap ...
A0838B2C 04 F0 1F E5 LDR PC, =dwMODdw
A0838B2C ; End of function j_dwMODdw_0
A0838B2C
A0838B2C ; 哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪?
A0838B30 8C 94 60 A1 off_A0838B30 DCD dwMODdw ; DATA XREF: j_dwMODdw_0r
剩下的就是网络信号了,可以从下面找到:
A08C0430
A08C0430 GetSignIconID ; CODE XREF: sub_A08BFE54+3Ap
A08C0430 70 B5 PUSH {R4-R6,LR}
A08C0432 2A 48 LDR R0, =0xA860E594
A08C0434 2A 4E LDR R6, =0xFFFF
A08C0436 00 68 LDR R0,
A08C0438 00 28 CMP R0, #0
A08C043A 05 D1 BNE loc_A08C0448
A08C043C 25 48 LDR R0, byte_A08C04D4
A08C043E 00 88 LDRH R0,
A08C0440 C0 07 LSL R0, R0, #0x1F
A08C0442 01 D4 BMI loc_A08C0448
A08C0444 30 1C ADD R0, R6, #0
A08C0446 70 BD POP {R4-R6,PC}
A08C0448 ; 哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪?
A08C0448
A08C0448 loc_A08C0448 ; CODE XREF: GetSignIconID+Aj
A08C0448 ; GetSignIconID+12j
A08C0448 26 4C LDR R4, =word_A0A318B2
A08C044A 6D F1 D8 E8 BLX GetNetSignValue
A08C044E 05 04 LSL R5, R0, #0x10
A08C0450 2D 0C LSR R5, R5, #0x10
A08C0452 33 F2 CF FF BL sub_A0AF43F4
A08C0456 00 28 CMP R0, #0
A08C0458 07 D0 BEQ loc_A08C046A
A08C045A 33 F2 DF FF BL sub_A0AF441C
A08C045E 02 28 CMP R0, #2
A08C0460 01 D1 BNE loc_A08C0466
A08C0462 18 34 ADD R4, #0x18
A08C0464 01 E0 B loc_A08C046A
A08C0466 ; 哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪?
A08C0466
A08C0466 loc_A08C0466 ; CODE XREF: GetSignIconID+30j
A08C0466 1F 4C LDR R4, =word_A0A318B2
A08C0468 0C 34 ADD R4, #0xC
A08C046A
A08C046A loc_A08C046A ; CODE XREF: GetSignIconID+28j
A08C046A ; GetSignIconID+34j
从上面那个GetNetSignValue那个函数入手,经过一个Thunk后,到达:
A00EEADC
A00EEADC sub_A00EEADC ; CODE XREF: GetNetSignValue+4j
A00EEADC ; DATA XREF: GetNetSignValue:off_A0A2D604o
A00EEADC 4F 48 LDR R0, =0xA86F0BB0;这个地址加8就是。
A00EEADE 80 68 LDR R0,
A00EEAE0 70 47 BX LR
A00EEAE0 ; End of function sub_A00EEADC
A00EEAE0
这里面的0xA86F0BB0,系统从它加8那里得到信号值。
完成了,天气太热了,不想抓图了! 支持楼主分享你的源文件..
这对学习补丁的移值有很大的帮助.. 文章是不是还没有完吧?? 在继续中!!!!!!!!!! 现在对补丁的运作已经有一点了解,
但想提几个问题.....对于补丁需要调用一些数据,而在其它机型应该如果找到相应的地址,
之前读者过一些关于移值补丁的帖,,知道可以从"特征码"入手....可不可以..提供一些"特征码"出来呢..
每一种机型的地址应该都不同吧...
特别是..A8xxxxxx.这一类..在RAM中的地址...... .....汗自己一个....看半天keil,不知道怎么用呢. ....知道怎么用了....半天才发现..... 一个小建议
#ifdef S6CV50
//for system function
const GETDATETIME getdate = (GETDATETIME)0xA0AA6A53;
const GETWEEK getweek = (GETWEEK)0xA082AD87;
const GetAkku getakku = (GetAkku)0xA1254830;
const f_uint2str uint2str = (f_uint2str)0xA0825687;
//const DWMODDW dwmoddw = (DWMODDW)0xA160948C; //because Keil's problem
//for system data with Absolute Location.
word Akkpct __at 0xA863FF08;
word net __at 0xA86F0BB8;
#endif
这种方法建议换成
#ifdef S6CV50
#include "S6CV50.h"
#endif
这样做的方法好处在
1. 在此基础上做多机型多版本移植时代码显得比较干净
2. 在S6Cv50.h中维护所有的地址即可,不用每次都根据程序所需的函数进行复制粘贴(在ADS中不会把多余无用的地址编译到目标中,Keil没试过,应该也不会吧)
3. 在手机版本升级后也方便根据该头文件更新 老大..想问一下..为什么生成的补丁会有
0x800078: 00000000000000000000000000000000
这样的一行??是由什么引起的??这应该要怎么去掉呢?? 原帖由 ShineGood 于 2005-6-24 09:47 发表
一个小建议
这种方法建议换成
这样做的方法好处在
1. 在此基础上做多机型多版本移植时代码显得比较干净
2. 在S6Cv50.h中维护所有的地址即可,不用每次都根据程序所需的函数进行复制粘贴(在ADS中不会把多 ...
这个建议很好,开发时没有考虑这个问题。下次再发这些东西时会改过来! 原帖由 devil_997 于 2005-6-24 09:47 发表
老大..想问一下..为什么生成的补丁会有
0x800078: 00000000000000000000000000000000
这样的一行??是由什么引起的??这应该要怎么去掉呢??
那个是因为一些对齐规则引起的,不用管它,不要过于在乎这一点空间! 问:
是怎么从工模的ACCU菜单 找到getakkuuint2str 和电池的百分比容量函数的? 找到ACCU菜单是通过他的显示的字符串。看了一下程序后发现,它是预先写好了整个字符串包括为数字预留的位置,使用的时候copy到buffer中,然后填充每一个位置。从这个发现了getakku函数,和uint2str函数,后面的memcpy是早就找到过的。百分比容量那个你找一下他在cap后面添的内容就知道了。 呵呵!好东东!学习学习! keil还需要设置什么?我没法编译。提示
--- Error: can't execute 'ARM-ELF-AS'
(用楼主的项目文件打开) 学习学习努力学习!!
页:
[1]