- 主题
- 25
- 阅读权限
- 30
- 积分
- 818
- 金币
- 1311
- 贡献
- 0
- 莓币
- 8
- 精华
- 0
- 帖子
- 406
- 注册时间
- 2010-2-1
- 最后登录
- 2011-8-19
- 在线时间
- 225 小时
  
升级   63.6%
TA的每日心情 | 无聊 2010-10-8 10:26:08 |
|---|
签到天数: 45 天 [LV.5]常住居民I - UID
- 1384
- 积分
- 818
- 金币
- 1311
- 莓币
- 8
- 在线时间
- 225 小时
|
发表于 2010-8-18 16:25:19
|显示全部楼层
本帖最后由 xubo_buaa 于 2010-8-18 20:35 编辑
黑莓OS5.0系统断网问题深入研究
原帖链接:http://www.feelberry.com/bbs/viewthread.php?tid=3414&extra=page%3D1
黑莓OS5.0系统的断网问题一直是困扰众多莓友的重要问题,对此我也一直是耿耿于怀。最近看到一些帖子说只要修改软件的联网方式,使用cmwap接入即可绕过断网故障,我自己通过对Opera和Gmail的修改也证实了这一点,而这也是我对找出断网的原因产生了浓厚的兴趣。
首先,我们来看两张截图:
这是断网后试图用Opera打开网页时从手机工程模式下的Event Log中截取到的错误信息,包含从Opera开始调用网络连接到发生错误的调用堆栈信息。可以看到,导致Opera无法建立网络连接的错误发生在net_rim_os-4.cod中SocketHelper类的checkDataConnectivity函数中。
从函数名来看,checkDataConnectivity就是在检测是否可以建立数据连接,那么这个函数为什么会抛出异常呢?让我们来看这个函数的反编译代码:
static public final checkDataConnectivity(
net.rim.device.cldc.io.utility.RIMConnectionParameters ); // address: 0 offset: 17525
{
enter_narrow // 0: dd (221)
ldc literal_110:"true" // 1: 28 (40)
aload_0 // 2: 3f (63)
getstatic_lib RETRY_NO_CONTEXT // RIMConnectionParameters // 3: 6e (110)
invokevirtual java.lang.String getParameter(
net.rim.device.cldc.io.utility.RIMConnectionParameters, int ) // pc=2 // 4: 01 (1)
invokevirtual_short .equals // idx=1 pc=2 // 5: de (222)
istore_1 // 6: 4e (78)
iload_1 // 7: 38 (56)
ifeq Label18 // 8: 93 (147)
invokestatic_lib assertRRISignatures( ) // ControlledAccess // 9: 08 (8)
goto Label18 // 10: a1 (161)
astore_2 // 11: 57 (87)
new_lib java.util.Calendar//java.util.Calendar // 12: b9 (185)
dup // 13: cf (207)
aload_2 // 14: 41 (65)
invokevirtual java.lang.String getMessage( java.lang.Throwable ) // pc=1 // 15: 01 (1)
invokespecial_lib java.io.IOException.<init> // pc=2 // 16: 06 (6)
athrow // 17: bc (188)
Label18:
getstatic net.rim.device.cldc.io.socket.SocketHelper.field_15022 // 18: 6d (109)
ifeq Label29 // 19: 93 (147)
iload_1 // 20: 38 (56)
ifne Label29 // 21: 96 (150)
invokestatic_lib net.rim.device.cldc.io.datarecovery.DataRecovery getInstance( ) // 22: 08 (8)
invokevirtual boolean isConnectionAvailable(
net.rim.device.cldc.io.datarecovery.DataRecovery ) // pc=1 // 23: 01 (1)
ifne Label29 // 24: 96 (150)
new_lib java.util.Calendar//java.util.Calendar // 25: b9 (185)
dup // 26: cf (207)
invokespecial_lib java.io.IOException.<init> // pc=1 // 27: 06 (6)
athrow // 28: bc (188)
Label29:
return // 29: 1f (31)
}
由代码可见,这个函数主要是为了调用DataRecovery.isConnectionAvailable()函数,如果返回false,则抛出异常。到此可以基本断定,日志中IOException的出现是由于DataRecovery.isConnectionAvailable()函数返回了false。
那么再来看DataRecovery.isConnectionAvailable()函数的反编译代码:
public boolean isConnectionAvailable( net.rim.device.cldc.io.datarecovery.DataRecovery ); // address: 0
offset: 16865
{
enter_narrow // 0: dd (221)
synch // 1: 12 (18)
aload_0_getfield ._errorLevel // ofs = -1 ord = 0 addr = 3 // 2: 67 (103)
bipush 4 // 3: 24 (36)
if_icmpge Label7 // 4: 99 (153)
iconst_1 // 5: 2c (44)
ireturn // 6: 18 (24)
Label7:
iconst_0 // 7: 23 (35)
ireturn // 8: 18 (24)
}
这个函数更简单,只要_errorLevel大于等于4,就返回false,否则返回true。
难道说如此困扰我们的断网问题,其原因只是因为这个变量的值大于等于4?!而这个值又是如何变成大于等于4的?
整个DataRecovery类中,仅有一个函数修改了_errorLevel的值:fileReport。
fileReport函数比较长,此处就不全部列出了,仅截取其中与_errorLevel有关的部分:
public fileReport( net.rim.device.cldc.io.datarecovery.DataRecovery, int, int, int ); // address: 0
offset: 16514
{
iload_1 // 9: 38 (56)
tableswitch :
tablesize=3
low=-1
table 0 default
-1 : Label129
0 : Label11
1 : Label42
Label11:
……
aload_0_getfield ._errorLevel // ofs = -1 ord = 0 addr = 3 // 19: 67 (103)
bipush 4 // 20: 24 (36)
if_icmplt Label28 // 21: 9b (155)
aload_0 // 22: 3f (63)
iconst_0 // 23: 23 (35)
putfield ._errorLevel // ofs = -1 ord = 0 addr = 3 // 24: 5f (95)
……
Label28:
aload_0 // 28: 3f (63)
iconst_0 // 29: 23 (35)
putfield ._errorLevel // ofs = -1 ord = 0 addr = 3 // 30: 5f (95)
……
Label42:
aload_0 // 42: 3f (63)
aload_0_getfield ._errorLevel // ofs = -1 ord = 0 addr = 3 // 43: 67 (103)
iconst_1 // 44: 2c (44)
iadd // 45: 7a (122)
putfield ._errorLevel // ofs = -1 ord = 0 addr = 3 // 46: 5f (95)
……
Label129:
……
return // 131: 1f (31)
}
由函数可见,当fileReport的第一个参数值为0时,_errorLevel会被重置为0,而参数值为1时,_errorLevel会递增。
也就是说系统在出现某种状况的时候,会用参数值1来调用DataRecovery.fileReport()函数,使_errorLevel值递增,当_errorLevel值累积到大于等于4的时候,我们的BB就断网了……
事实真的是这样吗?我们结合以下现象来进行验证:
1. 断网只影响GPRS、EDGE、3G、WIFI等联网方式,采用BIS、BES上网时不会断网;在连接字串中写明WAPGagewayIP等信息的也不会受断网影响
我在JDE开发环境内写了一个小的网络连接测试程序,并用调试方式在模拟器上运行,运用JDE的VM Byte Code调试能力,结合反编译得到的代码对整个网络连接的建立过程进行了跟踪调试,结果证明,采用BIS、BES方式和写明WAPGagewayIP信息的,其连接过程中完全不会调用到SocketHelper.checkDataConnectivity()或DataRecovery.isConnectionAvailable()函数,而其它连接方式下,无论是用socket还是用http连接,都会调用SocketHelper.checkDataConnectivity(),并且没有任何分支语句用来跳过该函数。
2. 断网只影响第三方应用程序,官方浏览器不受影响
说到这个问题,我们要回头再看看SocketHelper.checkDataConnectivity()函数的代码,这回给出整理后的java代码,注意其中标红的部分:
public final static checkDataConnectivity(RIMConnectionParameters parameters) throws IOException
{
boolean retryNoContext =
(parameters.getParameter(RIMConnectionParameters.RETRY_NO_CONTEXT).equals("true"));
if (retryNoContext)
{
try
{
ControlledAccess.assertRRISignatures();
}
catch (Throwable t)
{
throw new IOException(t.getMessage());
}
}
if (checkConnectivity && !retryNoContext)
{
if (!DataRecovery.isConnectionAvailable())
throw new IOException();
}
}
也就是说,如果连接字串中具备“;retrynocontext=true”属性,并且应用程序具备了RRI签名,就可以无视连接检测!而RRI签名是黑莓系统内核使用的签名,根本不对外公开!
接下来不用说大家也该猜到了,我在系统浏览器HTTPStackAdapter类中的private final HttpConnection routine_17191(int, String, String, BrowserConfigRecord, int, int )函数内找到了添加retrynocontext属性后再建立连接的代码:
getstatic_lib RETRY_NO_CONTEXT // RIMConnectionParameters // 87: 6e (110)
ldc literal_125:"true" // 88: 28 (40)
invokevirtual setParameter( net.rim.device.cldc.io.utility.RIMConnectionParameters, int,
java.lang.String ) // pc=3 // 89: 01 (1)
……
invokestatic_lib javax.microedition.io.Connection open( net.rim.device.cldc.io.utility.URL, int,
boolean ) // RIMConnector // 186: 08 (8)
这说明RIM知道网络连接有可能会有问题,却给自己开发的应用程序留了一个只有自己才能使用的后门!至于RIM是确实没有发现在DataRecovery的处理上有Bug还是有其它的原因大家自己猜想吧。
从以上这些分析中我们可以看到,DataRecovery中的_errorLevel的值是决定我们的BB断不断网的关键。然而这仅仅是浮于表面的部分,隐藏在后面的是整个DataRecovery以及fileReport函数的处理机制。这些我没有做更深入的分析,希望有感兴趣的朋友做进一步的研究。
下面再说说我是如何尝试解决断网问题,又是如何失败的。
第一次尝试:
既然调用fileReport函数并将第一个参数设置为0就可以将_errorLevel复位为0,那么我写个程序去调它一下不就行了!
查api文档发现DataRecovery类没有公开,就自己根据反编译的结果写个简单的类,因为是要作为api使用的,因此所有方法都不用具体实现。编译后把原先的net_rim_api.jar解包,再把编译好的DataRecovery.class打包进去,api就造好了。
接下来写好了程序,调试运行,一切OK!心情好的不得了!准备签名装手机时,噩梦到来,需要RIMAPPSA2签名(就是RRI签名)!这个我真的没有……不理会,把能签的签了,装机,结果因为缺少签名无法运行。
此次尝试到此彻底失败。
第二次尝试:
调用DataRecovery.isConnectionAvailable()函数是在net_rim_os-4.cod中,直接修改这个模块的二进制代码,使其永远不抛出异常不就行了。
找到模拟器目录下的net_rim_os.cod文件并解压,发现其数字签名是假的!这样修改内容就不会产生验证的问题了。
用HEX编辑工具对net_rim_os-4.cod进行了修改后,重新打包生成net_rim_os.cod文件并覆盖回原目录。启动模拟器后报错,奇怪,数字签名是假的怎么还会报错?将模拟器清除一下再启动,OK了!
接下来就是修改实际的net_rim_os.cod文件了。安装了484后再安装794混刷包,重新进行修改并覆盖原文件,最后刷机。过程一直很顺利,可结束的时候报出net_rim_os.cod文件签名无效,手机无法正常启动。怪了,怎么还有签名验证?再仔细一看,恍然大悟,模拟器里的文件签名是假的,可真正的刷机文件签名可是真的……
此次尝试到此又彻底失败。
第三次尝试:
既然以上两次尝试都败在签名这里,那么有没有可能从根本上把签名验证废掉?
在net_rim_cldc中找到了verifySignature函数,把这个函数咔嚓掉不就可以跳过验证机制了。重新修改后再刷机,再次失败,这回屏幕上显示一个错误代码,让重装软件。
此次尝试到此再次彻底失败。
现在我已经彻底没招儿了。
哪位朋友如果与黑莓官方的人有联系,可以将上面的问题向他们反映反映。或者哪位英文比较好,帮忙把本文翻译翻译,贴到RIM官方论坛之类的地方,希望能引起他们的注意。
再补充一点:
我对比了OS4.7与OS5.0,发现4.7版本中根本没有SocketHelper类,在建立网络连接的过程中也不会调用DataRecovery.isConnectionAvailable()函数。也就是说验证网络是否能够连接这个机制是从OS5.0系统开始引入的,这也是OS4.7或以下系统不会断网的原因。
|
|
-
总评分: 金币 + 67
查看全部评分
|