现在的位置: 首页 > 综合 > 正文

[转重要]黑莓开发【OS5.0】断网问题

2019年10月05日 ⁄ 综合 ⁄ 共 9007字 ⁄ 字号 评论关闭

黑莓OS5.0系统的断网问题一直是困扰众多莓友的重要问题,对此我也一直是耿耿于怀。最近看到一些帖子说只要修改软件的联网方式,使用cmwap接入即可绕过断网故障,我自己通过对uc和Gmail的修改也证实了这一点,而这也是我对找出断网的原因产生了浓厚的兴趣。

这是断网后试图用uc打开网页时从手机工程模式下的Event Log中截取到的错误信息,包含从uc开始调用网络连接到发生错误的调用堆栈信息。可以看到,导致uc无法建立网络连接的错误发生在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或以下系统不会断网的原因。 

wap接入点的时候并没有将代码完整,所以导致断网,请供应wap接入点的选择,并且在写程序的时候,将代码CMWAP连接方式填写完整,即包含下面的关键字
:deviceside=true;WapGatewayAPN=cmwap;WapGatewayIP=10.0.0.172;WapGatewayPort=9201

并不是非要选择cmwap连接方式,你可以设置3种模式供用户选择,这里只是强调在编写cmwap模式时,请将代码填写完整,原作者发现黑莓版UC没有将代码填写完整。

  建议大家暂时使用4.6版的rom。等第三方应用程序的开发人员真正按照5.0版的开发时再使用。

今天仔细看了开发手册其中说到:
  调用 Connector.open(),将 udp 指定为协议,然后将返回的对象转换为 DatagramConnection 对象,
  以打开数据报连接。
  (DatagramConnection)Connector.open("udp://host:dest_port[;src_port]/apn");
  要通过数据报连接接收数据,请在数据报连接上调用 receive()。 receive() 方法将阻止其它操作,直
  至收到数据包。 如果未收到答复,则使用计时器重新传输请求或关闭连接。
  
uc使用的这个方法收数据,当收不到数据时。uc就不停地占用端口资源,最后第三方软件就都上不去了。

BB的内置程序是使用另一个方式调用叫做:无线接入系列
  
第三方程序都没有使用这个方法,当第三方程序收不到数据时,应该自己做一个定时器,超时后由第三方程序自己关闭连接,以便再次连接。
但是这些开发者没注意到这个问题,以为捕获了异常就可以了。这是开发者的问题。

BB的开发者肯定知道这个问题,所以BB内置的程序都做了定时器来判断连接超时的问题。
建议大家暂时不要用QQ2010,shangmail,uc等软件。这些软件运行一段时间就会断网的。
还有一个问题,这些软件不是你在菜单里选择的时候才运行的。
BB启动的时候他们后台的核心模块就已经运行了。不信你有这些软件的话,开机一定时间就断网了。

根据分析结论是第三方应用程序造成的:
  当手机没有信号或信号不好时,总之是不能进行网络数据访问时。第三方应用程序不断调用BB的HTTP协议类。
  例如
  1 uc在没有信号时会每分钟调用20次,也就是说每3秒调用一次。
  2 BB的HTTP协议类会不断地返回空指针异常。
  3 但是uc并没有把调用的线程关闭掉。
  4 那就是说当你没有信号时,uc每分钟会增加执行20个线程,那么5分钟就是100个线程。
  5 你在电脑上试试开100个IE浏览器,你的机器肯定完蛋了。
  6 所以5.0系统会做限制的,就是每个程序也许只能开有数线程。超过了就不能再开线程了。
  7 所以这时候你的uc已经不能再开线程访问网络了。返回的全是空指针错误,也就是线程没有被创建。
  8 uc当然不能再联网了。
  9 所以BB内置的程序都没有这样的问题。怎么都不会断网的。
10 所以你重起BB时又可以访问了,就像重起电脑一样。
11 为什么4.6版没有问题呢。其实在4.6版时第三方应用程序也是在不断的调用,但是线程都会被关闭。
12 因为4.6和4.5的开发平台是一样的。4.5平台已经很成熟了,开发者经验 也比较丰富,所以不会出现太多问题。
13 但是开发者比较懒,还是沿用4.5的方法开发。5.0版的平台完全不一样,API都变化了很多。
14 BB在开发者说明中关于访问模式在开发手册上已经建议使用5.0版的新方法。

uc断网后,再连接网络时,速度非常快的就返回了错误 40
经查后台log 直接抛空指针异常了。
而且没有调用系统的API。直接异常。

ROM里自带的归类为 附加程序。
自己安装归类为 第三方。

经测试 附加程序 中的绝对不断网。
第三方的会断网。

建议第三方应用程序开发者好好学学BB新的平台中应用程序进程和线程的控制方法的改变。

结论:
1 在连接字串中写明WAPGagewayIP等信息的不会受断网影响
2 当fileReport的第一个参数值为0时,_errorLevel会被重置为0,而参数值为1时,_errorLevel会递增。
也就是说系统在出现某种状况的时候,会用参数值1来调用DataRecovery.fileReport()函数,使_errorLevel值递增,当_errorLevel值累积到大于等于4的时候,我们的BB就断网了……可以用内置的程序做一个定时器来判断连接超时的问题。当_errorLevel等于3的时候断开网络连接,使之不超过4。
3 8900断网绝对不是硬件问题。
4 绝对不是ROM问题,断网只有第三方应用才断,BB的内置软件不会断网的。
5 第三方应用程序的开发者还没学会使用新平台。

抱歉!评论已关闭.