从华为方舟编译器看一种JavatoC语言解释器的实现

小结:

1、多语言联合优化编译器
Android应用市场中TOP级APP,95%以上都是由多种开发语言来实现的。这些APP的逻辑部分是Java代码,但是为了增强app应用的安全性、可靠性、兼容性,开发者会用效率更高的C语言写库,供Java来调用。但跨语言调用,一定会导致额外的性能开销,并且调用的应用越重、开发语言越多,对系统性能的影响就会越明显。
方舟编译器可将不同语言代码在开发环境中自动编译成一套可执行的文件,直接消除了跨语言调用的开销,大大提升了APP应用在运行环境中的高效执行,这仅仅是第一步

2、虚拟机的性能决定了安卓运行瓶颈
Android从诞生起,就是一个开源系统,要面对极其复杂的开发环境与硬件适配宽容度。因此选择了Java这个成熟语言,并由此带来了Java 虚拟机——相当于虚拟了一个Java运行环境,支持跨开发语言与跨平台操作。但同时问题也很明显:任何一个Android手机只要安装应用就会自动配置虚拟机,不仅占用了额外的系统资源,更重要的是,每个应用都需要通过虚拟机来完成对机器码(二进制码)的编译,然后再由虚拟机发出执行命令完成应用服务。
直接干掉了虚拟机
方舟编译器最重要的革新,就是直接干掉了虚拟机,在开发环境中就完成了全部代码的编译,无论应用编程是由Java写的还是C语言写的,都不需要再次由虚拟机互相编译。手机安装应用程序后,无需再依赖虚拟机资源,即可全速运行程序调度命令完成服务,由此消除了虚拟机动态编译的额外开销,带来系统运行效率与流畅体验上的极大提升。数据显示,EMUI 9.1仅仅对系统组件System Server应用了华为方舟编译器后,就带来了系统操作流畅度提升24%,系统响应性能提升44%。

方舟编译器的最大技术突破,是把动态语言部分全部变为静态编译,其核心是一定要对Java语言动态特性非常深入理解。

3、上层业务代码不需要改动
方舟编译器从多年前决定做的那一刻起,就立下了“上层业务代码不需要改动,开发者只通过华为方舟编译器做可以重新编译”的目标。现在开发者不论用Java语言去开发新的APP,还是自己写的C语言库,这两种方式都不需要变,在开发构建阶段就为开发者提供快速的集成编译环境,降低了开发者学习和使用成本。

 

 

 

余承东三年前“吹牛”华为体验超苹果,方舟编译器将要实现了 https://baijiahao.baidu.com/s?id=1632292071046828780&wfr=spider&for=pc

文丨壹观察 宿艺
2016年11月华为Mate 9系列发布会后,余承东在媒体专访时首次表示:“华为手机目标在体验方面也超越苹果”。
此时几乎没人相信。甚至有媒体当场发微信朋友圈说:“嘴哥又吹牛了”。
即使华为Mate 9系列搭载的EMUI5.0解决了安卓长期使用不卡顿难题,但鉴于安卓系统与苹果iOS之间在运行机制、效率与系统流畅度上的明显差异,“体验超苹果”似乎也只能是个梦想。
外界不知道的是,华为消费者业务此时已成立了“编译器与编程语言实验室”,目标是将安卓系统底层彻底解构,通过全新编译器从根本上一劳永逸地解决问题。编译器被誉为软件行业的“明珠”,可以说是连接人类世界与数字世界的桥梁,也是计算机与互联网技术最核心的底层技术之一。
其中的难度、风险与挑战,可想而知。
时隔两年半,2019年4月11日在上海的华为P30系列发布会上,华为宣布EMUI 9.1首先搭载了“方舟编译器”与“EROFS超级文件系统”。业界猛然发现,华为不仅实现了对安卓底层运行机制的彻底重构,甚至目标通过开源,联合Google、手机企业与开发者共同推动安卓生态的颠覆性“换血”—— 唯有如此,安卓系统从诞生起就存在的“顽疾”才能被根除。
余承东“体验超苹果”的目标,显然并不是空口吹牛。
方舟编译器:重构安卓底层
在此之前,几乎所有安卓手机企业在操作系统方面最大的努力,无非就是争夺安卓最新系统版本的适配速度,以及在其基础上推出的UI/ROM优化方案。
这相当于对原有安卓系统针对中国市场的修修补补,不仅造成了全球安卓碎片化最严重的市场,并且还因掺杂各种广告收益而被用户吐槽为“ADUI”,相当于负向优化。
只有华为选择了一条最难、具有极高风险的路:重构安卓底层。
华为消费者BG软件部总裁王成录博士对《壹观察》透露,相对传统Android系统,方舟编译器实现了对系统底层的四大“换血”:
第一,业界首个多语言联合优化编译器。
Android应用市场中TOP级APP,95%以上都是由多种开发语言来实现的。这些APP的逻辑部分是Java代码,但是为了增强app应用的安全性、可靠性、兼容性,开发者会用效率更高的C语言写库,供Java来调用。但跨语言调用,一定会导致额外的性能开销,并且调用的应用越重、开发语言越多,对系统性能的影响就会越明显。
方舟编译器可将不同语言代码在开发环境中自动编译成一套可执行的文件,直接消除了跨语言调用的开销,大大提升了APP应用在运行环境中的高效执行,这仅仅是第一步。
第二,干掉了安卓最关键,也是长期被诟病的“虚拟机”。
Android从诞生起,就是一个开源系统,要面对极其复杂的开发环境与硬件适配宽容度。因此选择了Java这个成熟语言,并由此带来了Java 虚拟机——相当于虚拟了一个Java运行环境,支持跨开发语言与跨平台操作。但同时问题也很明显:任何一个Android手机只要安装应用就会自动配置虚拟机,不仅占用了额外的系统资源,更重要的是,每个应用都需要通过虚拟机来完成对机器码(二进制码)的编译,然后再由虚拟机发出执行命令完成应用服务。
打个比较直观的比喻,这就相当于虚拟机里有两个翻译在工作,一个需要把法语翻译成俄文,再由另一名翻译成中文,这个过程两个翻译的工作是无法并行的,所以观众听到的内容一定断断续续。也就是说,安卓系统这种卡顿感无法避免,只能优化,但无法彻底解决问题。因此才有开发者说,虚拟机的性能决定了安卓运行瓶颈。这也是Google虽然在后期不断对编译器进行升级,但依旧无法解决系统卡顿顽疾的根本原因。
同样重要的是,虚拟机的统一回收内存机制也是“BUG”般的存在。安卓初期并没有对开发者有类似苹果iOS严格的开发机制,导致很多开发者都希望自己的应用尽可能地使用最大内存,以获得更好的用户体验。甚至有开发者利用这一规则漏洞,让其APP具备后台强行唤醒功能或者自家应用的“全家桶”唤醒机制。而Java虚拟机模式提供的内存GC(垃圾回收)机制,在内容垃圾集中回收时需要短暂中断应用运行。两种情况在中国市场遭遇,无疑进一步加剧了应用的随机卡顿问题。这也是很多开发者甚至是手机企业自己都不相信安卓手机可以“体验超苹果”的重要原因。
方舟编译器最重要的革新,就是直接干掉了虚拟机,在开发环境中就完成了全部代码的编译,无论应用编程是由Java写的还是C语言写的,都不需要再次由虚拟机互相编译。手机安装应用程序后,无需再依赖虚拟机资源,即可全速运行程序调度命令完成服务,由此消除了虚拟机动态编译的额外开销,带来系统运行效率与流畅体验上的极大提升。数据显示,EMUI 9.1仅仅对系统组件System Server应用了华为方舟编译器后,就带来了系统操作流畅度提升24%,系统响应性能提升44%。
当然,虚拟机之前被认为是安卓系统的核心,也是谷歌最敏感的部分。国内之前有其他互联网企业因为想用自己的虚拟机机制进行取代而遭遇谷歌的长久封杀。华为虽然没有进行替换,但却是从根本上改变了安卓系统的核心运行规则,因此谷歌的态度至关重要。
华为消费者BG软件部总裁王成录博士
王成录对《壹观察》表示,这实际上要解决两个维度的问题:第一个是作为不同编程语言的“翻译官”,方舟编译器本身的能力怎么样。方舟编译器的最大技术突破,是把动态语言部分全部变为静态编译,其核心是一定要对Java语言动态特性非常深入理解。“就像一个优秀的翻译官一定对汉语言文学理解的非常到位,然后再对英语理解非常到位。”从EMUI 9.1目前带来的系统流畅度与应用响应速度来看,方舟编译器的技术优势非常突出。
第二个,确实需要谷歌的认可。王成录透露称,谷歌是华为的战略合作伙伴,对华为在系统优化上的能力一直非常认可,之前华为在F2FS的文件系统、安卓优化等方面都获得了谷歌的认同,并且延用至后来版本的系统开发中,让更多的安卓手机企业因此获益。在促进安卓体验升级方向上,华为与谷歌的态度都是开放的。方舟编译器在推动之初,华为就与谷歌进行了深入的沟通,目前EMUI的方舟编译器版本首先在中国市场使用,这个也是谷歌认可的。
当然,改变整个安卓运行机制甚至是生态,需要谷歌、所有的开发者与手机企业共同推进,这也是方舟编译器选择开源的重要原因。只有生态链产业共同推动,才能真正实现安卓系统体验真正全面超越iOS这一终极目标。
干掉了虚拟机,意味着系统的内存回收机制也会带来变革。在方舟编译器里,编译时为程序配备了及时回收的内存处理机制,即在程序执行过程当中同步实时处理回收。所以方舟编译器的垃圾回收及时又彻底,并且不需要停止进程,解决了应用随机卡顿这一另外的“顽疾”问题。
第三,可针对不同应用灵活编译优化。
如之前所说,翻译器本身做的好不好,同样是决定系统运行效率的核心底层原因,最重要的衡量标准是能不能给程序带来性能的明显提升。
同样以“翻译官”举例,单独单词直译并不是难点,难得是要翻译的又快又好,还要适合对方语音习惯。比如中文“夫妻肺片”怎么翻译成英文?如果硬翻译,那就是“丈夫和妻子的肺片”,老外们听着会无比惊悚。而美国《GQ》杂志将其翻译为Mr and Mrs Smith(电影《史密斯夫妇》)就很容易理解和记忆了,甚至一度被美国网友评选为“年度开胃菜”。
因为不同应用的优化诉求不一样,而现有的安卓编译,大部分代码在虚拟机环境运行,虚拟机的创建来自于同一套“模板”,难以作深度的优化。方舟编译器不一样,每个应用编译优化方案可以自定义,分别形成不同应用优化后的机器码,相当于说,我们给每一个有追求的应用开发者,都有了一个把自己的应用做得更好的机会,不再受限于Android虚拟机的限制。
方舟编译器通过了大量的优化和调整,一方面对Java语言深入了解后再细致重建,另一方面对机器指令顺序结构理解要求非常清晰,这两方面的匹配度越高,编译器效率越高。一个好的编译器,开发者一行代码都不需要修改,性能提升10%到20%。
第四、开发者学习和使用成本低。
做操作系统的微软,在Windows Phone上为何会失败?其中的原因很多,但开发者学习和应用迁移成本高,收益低是其中一个非常重要的原因。包括三星、英特尔等企业,以及中国互联网企业在内也都在移动互联网时代初期推出了各种操作系统,但无一成功。开发者的支持与应用生态丰富度,是绕不过去的门槛。
《壹观察》特意向王成录问及了这一问题。王成录回答称:方舟编译器从多年前决定做的那一刻起,就立下了“上层业务代码不需要改动,开发者只通过华为方舟编译器做可以重新编译”的目标。现在开发者不论用Java语言去开发新的APP,还是自己写的C语言库,这两种方式都不需要变,在开发构建阶段就为开发者提供快速的集成编译环境,降低了开发者学习和使用成本。
王成录还对《壹观察》透露称,在方舟编译器发布当天,华为软件的开放实验室 电话被开发者打爆了,业界在安卓顽疾问题上的关注度之高,远超之前想象。
华为“软能力”的“十年磨一剑”
华为过去给的外界印象,一直是“科技硬核”企业。
EMUI领域的巨大进步,让大众用户越来越多的感受到华为科技的“软能力”。
华为在EMUI方面的投入,最早是2012年开始的,也就是决定华为手机业务未来的那场“三亚会议”之后。华为创始人任正非在会议上提出:“我认为在终端上,我们创新不够、能力不够,自己要抓住自己的优势... ”,“要好好想想,我们的战略是什么,怎么才能胜利”。
之后被业界广为所知的是,华为通过P1、D1、Mate 7、P9、Mate 9、P30等一系列硬件机型的“不抛弃、不放弃”地持续奋斗。华为软件能力,同样是其中最重要的支撑部分,“板凳要坐十年冷”的坚持与努力同样值得肯定。
外界对华为手机“软能力”的密切关注,首先是从EMUI 5.0开始的。在完成了硬件的初步重大创新之后,华为就清晰地意识到安卓系统的顽疾问题,将是自己与苹果进行用户体验较量中未来至关重要的短板。
华为为此确定了两步走战略:第一步,是要“剖解”安卓,真正掌握底层核心技术,这就有些像是中国歼11战机对苏27战机的“逆向工程”。
从2015年开始,华为开始“花大力气”构建自己的操作系统底层研发能力,最核心的是把系统软件架构做了更好的重建,将系统底层、中间层,以及应用层之间的分割更为清晰。将一些华为需要的模块进行底层优化和增强,实现Android系统“每一个模块代码华为都清晰掌握”,并在此基础上开始具备自己真的是系统底层“换心手术”式优化。从用户端带来可感知的体验就是“天生快一生快”,以及“ EMUI 5.0的18个月不卡顿”。
要知道,Android系统本身并不只是为智能手机定制,系统代码超过1亿行,非常臃肿。华为为此投入了超过数百名系统工程师进行“定向攻坚”,仅此一项投入就超过了很多国产手机企业的全部软件研发人员。配套的研发硬件环境投入也是惊人的,仅老化测试装置成本就超过5000万美元。在EMUI 8.0发布之后,连Google都认为这是一项“非常有挑战”工作,并在之后的Android系统版本升级中借鉴了华为EMUI的经验。
第二步,则是从编译器的底层技术完成对安卓的重构,包括干掉之前被谷歌视为安卓核心的虚拟机。
王成录在接受《壹观察》专访时透露,华为在翻译器上的投入,最早始于十年前。2009年华为就创建了编译组,因为海思做了芯片以后,需要有自己的编译器才能真正发挥芯片的能力。从2013年开始,华为推出了自己的编译器HCC,用在基站领域,这可以说是方舟编译器的前身。到了2014年,在王成录的“三顾茅庐”之后,以Fred Chow等众多“大神级”的编译器领域的资深专家加入华为,同时大批清华、中科院的博士加入,搭建了华为编译器的主架构。2016年,有了成功的实践经验以后,华为2012实验室成立了编译器与编程语言实验室;2019年4月,终于推出了华为方舟编译器。
也就是说,即使华为这种在技术上投入最多的科技企业,在方舟编译器这一“软件行业明珠”的重投入也已超过了十年。背后是华为一代软件研发人员的青春与大技术理想主义,这是一个真正的“板凳要坐十年冷”的巨大研发工程。甚至可以说,这是继海思处理器之后,华为终端终于在系统软件方面也具备了真正的行业领跑与创新能力,而不仅仅是“甩别人一两条街的技术突破”。
迈向全场景智慧化生态:华为目标远不止超越苹果iOS
华为EMUI 9.1首先搭载的EROFS超级文件系统,同样是华为“软能力”的一个重要体现。但方舟编译器的光芒,让外界有些忽视了它的重要作用。
EROFS超级文件系统是伴随着存储介质的发展一步一步走过来,存储介质不断的演进,让这个文件系统不断的跟着演进,数据在这个存储介质上存储读取更好更快更安全。2016年搭载EMUI 5.0的华为Mate 9,首次在业界规模商用F2FS文件系统,替代了传统的EXT4文件系统,令用户分区的文件读写流畅度提升20%。EROFS超级文件系统通过高效的压缩算法加持,带来随机读性能提升,EROFS的做法是保证每次压缩一块,正好是一个固定大小的存储块(即压缩后的文件和存储的最小单元边界一致),这样几乎没有浪费,不仅性能提升随机读性能的20%,ROM空间还节约了2GB。以华为P30 Pro 128G为例,系统初始空间相比Ext4节省2GB,相当于用户可以多存1000张照片或500首歌曲。
谷歌之前在安卓上使用的Ext4是一个读写双向的文件系统,为了防止读写系统区,要加两把锁才能锁住。这两把锁加上,这个机制就变得更复杂了。EROFS超级文件系统天然只读设计,系统分区不可被三方改写,因为一旦系统区被写就意味着系统被攻破了,所有运行管理保证它的正确性,所以不能被改写意味着更安全。
也就是说,从EMUI 9.1开始,华为手机上的安卓系统不仅更流畅,对开发者和用户体验更友好,而且更安全。这三个方面,都是传统安卓相当于苹果iOS的弱项。
随着外界对华为“软能力”的深入了解,一个困惑的问题是:华为实际上已经完全有能力自建一套操作系统,华为内部是不是存在另一个“Plan B”的系统备案?近期包括任正非、余承东在内,也都在接受外媒采访时肯定了这一消息。当《壹观察》问及这一问题时,王成录笑称:“操作系统从技术上对于华为来说不是问题,难点在于丰富的生态”。
这一回答,实际上也可以从另一个维度推断出,华为正在用自己的系统级核心技术能力,来对安卓进行剖解、组建、重构,从而推动解决安卓从诞生以来就存在的“病根”。这就很好理解在华为方舟编译器发布同时,余承东宣布将此项技术面向全业界进行开源。因为华为明白,EMUI如果要完成对iOS的全面超越,不仅仅要依靠自身对系统底层的核心技术突破,同样需要开发者与合作伙伴的共同推动。
更令《壹观察》关注是,华为在软能力上已经深入到编译器这一连接人类世界与数字世界的桥梁的核心底层技术,其目标应该远不止是智能手机操作系统体验超过苹果iOS这么简单。因为面向5G、AI、IoT等重大技术融合变革,一个可以清晰预判的未来就是:以iOS+本地APP主导的苹果移动互联网生态,正在被万物互联时代“无处不在的场景化智能服务”所取代。那么华为EMUI又会走向什么样的未来?
这个问题实际上已经有了答案。
余承东最近在一次媒体专访中直言:“未来五到十年,华为消费者业务的核心战略就是全场景智慧化生活体验升级,不是之一而是唯一”。
在此之前,华为已将 HiAI智慧终端计算平台,通过芯、端、云三个层面向外界开放。Huawei HiAI开放能力不断增强,支持算子数量增加至147算子,API上线数量增加33个API,接入原子化服务超3200项。已经有很多合作伙伴利用HiAI开放平台给消费者提供了很多创新体验,比如Storysign利用HiAI的API能力帮助残障人士进行无障碍的阅读。通过开放的全球生态系统,以及1400+生态合作伙伴和560000+的开发者,用户将会获得更多更丰富的智慧场景体验。
HUAWEI HiLink平台目标连接全球最广的IoT硬件设备覆盖,HUAWEI Ability Gallery快服务智慧平台目标在多场景和多入口精准分发第三方的直达服务。
而跨系统、跨硬件终端的方舟编译器,可以帮助所有智能硬件企业、开发者与系统厂商,更高效的连接起硬件、数据、与服务,共同构建一个全场景智慧化的万物互联时代。
这也是苹果想迈出,但受制于封闭的iOS体系目前无法迈出的一步。
这应该才是余承东“体验方面超越苹果”的精彩未来。

[原创]从华为方舟编译器看一种JavatoC语言解释器的实现-编程技术-看雪论坛-安全社区|安全招聘|bbs.pediy.com https://bbs.pediy.com/thread-251138.htm


 

 

(一)背景介绍

最近网络上流传的华为方舟编译器很火,随着热度的提高,华为也将技术原理做了简单披露。我们来看官方的描述,原文链接:https://baijiahao.baidu.com/s?id=1632292071046828780&wfr=spider&for=pc。此时此刻,这套编译器华为还没有开源。

 

作为安全行业的从业者,我想大家很清楚安卓下的所谓C语言就是JNI(JAVA Native Interface)。安全开发者为了实现关键的安全防护功能自我保护,我们通常将功能实现在JNI里,然后用NDK编译成so文件,再通过对so加壳或者虚拟机化,进一步自我保护,这样可以极大增强上图提到的“安全性”。上图传达出的第二条信息是,该编译器将java和C(JNI)编译成一套可执行文件,到底是什么可执行文件呢?我们再往下看

 

 

什么是干掉虚拟机?我们知道,语言分成编译型语言和解释型语言,前者的典型是C,苹果是ObjectC,编译器将C语言直接编译成机器指令后由CPU执行,后者的典型就是Java语言,即由JVM里的解释器来执行java字节码,这个解释器就是虚拟机的核心之一。

所以我们不妨大胆猜测:所谓“干掉虚拟机”,是指将java代码转换成C代码,即将解释执行转换成编译后执行,由于C的执行速度比Java语言快,因为编译执行总是快于解释执行,从而实现了性能的提升。

上图传达出的信号是:这套编译框架甚至还可以优化java语言的执行效率,也就是java代码的编译优化。


 

 

上图所反映的法律和知识产权问题,据说谷歌和华为真正的老板其实是同一群(个)人,不多谈。

所以总结下来:华为的所谓“方舟”编译框架是:输入用户源代码,先将其中java语言进行优化,之后将它转换为C语言(JNI),从而提高执行效率。所以这套编译器的本质,我的猜测是:JAVA to C,或者JAVA to JNI,方舟编译器的本质是,一个基于编译器的语言解释器。

 

 

其实国外有很多成熟的JAVA to C/Native编译框架,不论是商用的还是开源的,比如Toba,Vortex,Marmot,IBM High performancefor Java Compiler,TowerJ

,superCede等等,都可以是可以提供大量的借鉴的。搜索java native compiler了解更多。另外国内很多安全厂商,据说几年前就能提供类似的且比较成熟的方案,不排除华为“借鉴”了友商的方案,工程框架确定后,凭借数量众多的工程师和强大的执行力,自身的产业号召力和国家资本的帮助迅速将其商业化和产业化。但是能否如华为宣传的数据如此之漂亮, 实际性能,用户的实际体验究竟能提升多少,作为从业人员,我本人并不那么乐观。而且这种转换很有可能降低系统的稳定性和兼容性。

 

 

那么接下来才是本文的重点:作为技术人员,我们要如何实现这套编译器呢?在这里,我提出一种方案,可以完整覆盖上述的所有特性,一样可以实现华为所宣传的方案,希望能抛砖引玉,分享不同的思路的效果。条条大路通罗马。


 

 

(二)JAVA语言的编译优化

 

第一步要实现华为宣称的“一个好的编译器,开发者一行代码都不需要修改,性能提升10%-20%”。

 


 

 

绝大部分安卓APP超过99%的代码都是JAVA语言写的,所以对JAVA的优化尤为重要。我们使用soot框架来优化,Soot是什么呢?它是由加拿大麦吉尔大学Sable Reasearch Group维护的,用来优化java程序的一套成熟的框架,这套框架的文档非常之全,可以让新人快速上手。


 

 

正如上图所说:java有很多优秀特性,但执行效率不如C/C++,为了优化java执行效率,soot应运而生。

Soot的Github地址是https://github.com/Sable/soot,文档不限于以下https://github.com/Sable/soot/wiki/Tutorials

Soot的原理是什么呢?和LLVM相似,编译框架会将原始代码转换成一种中间语言。由于中间语言相当于一款编译器前端和后端的“桥梁”,无论是学习Soot还是LLVM,透彻了解他们的中间语言无疑是非常必要的工作。 Soot共有4种中间语言:BafBody,GrimpBody ,ShimpleBody  和 JimpleBody,每一种中间语言都可以进行优化。

BafBody:一种字节流形式的java字节码表示方法,易于操作和维护;

Jimple:一种规范的三地址码形式的中间表示;

Grimple:一种更加适合反编译的Jimple表示形式

对于Baf的表示如下所述


 

 

Baf是基于栈的表示,是一种较为初级的IR


 

 

在Jimple语言中,用的最多的就是statement型语句,这15种如上图所述。我们知道在java字节码中,总共有将近200多种指令,而现在的Jinple却只有15种statement,是不是大大简化了?并不是只有15中,而是其他种的指令要少很多,用到频率低。

那么soot的中间语言Jiimple长什么样的呢? 我们再看一个例子


 

 

这是转换之前的java源代码


 

 

这是转换之后的Jimple代码

是不是很容易就能看懂呢?我们可以简单总结一下Jimple的语法特性:

1.使用$开头的局部变量表示的是一个栈内的地址,它不表示一个真实的局部变量。而不以$开头的才是真正的局部变量。以$开头的变量由于不是原java代码里真实的变量,存在于堆中,所以是一种“堆媒介”

2类似int x = (f.bar(21) +a) * 长指令会被分隔成$i4 = $i3 + i0 和 i2 = $i4 * i1这样的三地址形式的指令。 什么是三地址码?就是形如x = y op z 型的指令

3参数值和this指针使用IdentityStmt形式的表达式被分配到一个具体的局部变量中,比如i0 := @parameter0: int; 以及r0:= @this: Foo。进一步讲,由于x := @parameter0符合形如identityStmt下的local := @paramrtern:type可知paramrter0就是第一个参数,local代表局部变量(或者立即数)了

要想继续推进,首先要对jimple代码足够熟悉。

 

上面的两个表,详细阐述了jimple语法的规范,从上面我们可以看到上面所说的15种statemnt和其他的几种指令,不得不说文档很齐全很细致,比我之前用的JS编译框架esprima好很多。总之只要根据官方提供的手册,可以很清楚搞清楚java和jimple的一一对应关系,自然就熟悉了Jimple语法。

这种形式的中间语言优势很多,因此也是很多自定义优化步骤和自由化工具处理的对象。我们的节点转换器也不例外,就是以Jimple代码为基础进行转换的

如果您熟悉编译器的优化,相信您已经被三地址码这种简洁又规整的形式吸引了。没错,但是soot还提供了一种更加适合编译优化的语言:shimple。它是一种静态单赋值型的语言(SSA),它保证了每一个局部变量只有一个赋值,将极大简化分析。除此之外,他和Jimple还有一个巨大的区别是它还引入了Phi节点,所以它是特别适合用来做优化和分析的一种语言了。但是这里我们不做过多介绍,编译优化,有大量成熟的算法可供借鉴,这里我们不多谈,我们还是选择Jimple作为转换对象.

关于Jimple的语法规范,官网上有详细的文档供查阅。


 

 

(三)JAVA to C

现在尝试去实现华为宣传的第二个特性:彻底抛开虚拟机。

 

只要能将java字节码由解释器解释执行,转换为编译后直接由CPU执行,是不是自然就实现了上面的功能呢?就“干掉”虚拟机呢?但是怎么转化呢?由于前面已经能够将200多种字节码指令转换成20多种指令,如果我们将这不超过20种指令的JImple,做个等价转换,也就是一个节点(node)的解释器,是不是就可以实现JAVA toC呢?也就是说,首先将java代码进行简化,分成有限的几种指令,接着只要根据每一种指令,转换成JNI语法,最后再编译成so,让原本的java字节码指令转为执行so里的JNI,就实现了从java代码到C代码的转换

转换成JNI后的代码不仅更加安全,还能提高执行效率,和上述完全吻合!

现在我们简单的看一两个转换示例:

假设我们有一个被测试的类,如下所示:

1
2
3
4
5
6
7
package demo;
public class have_a_try {
    public static void main(String[] args) {
        System.out.println("Hello World");
        return ;
    }
}

把它转为jimple

 

 

 

 

1
2
3
4
5
6
7
8
9
10
11
12
13
    public static void main(java.lang.String[])
    {
        java.lang.String[] r0;
        java.io.PrintStream $r1;
 
        r0 := @parameter0: java.lang.String[];
 
        $r1 = <java.lang.System: java.io.PrintStream out>;
 
        virtualinvoke $r1.<java.io.PrintStream: void println(java.lang.String)>("Hello World");
 
        return;
}

 

现在开始转换,由于涉及很多技术细节,比较敏感不详细解释。首先,将类下的方法名称 转换成JNI能识别的形式,

JNIEXPORT jobject JNICALL Java_demo_have_1a_1try_main

我们看后面的Java_demo_have_1a_1try_main,静态加载时,由于需要具体指明类所在的路径,会采取这种形式,这样就免去了动态加载时使用JNIOnload注册函数步骤,但需要注意,要避免重名,命名的不合法,区别构造函数,,一些特殊字符也需要特殊处理等等。从函数名到JNI函数名要不断试错,找出合适的命名规则

然后在参数传入this指针,JNI环境env,对象jobject,JNIEnv类型实际上代表了Java环境,通过这个JNIEnv* 指针,就可以对Java端的代码进行操作。

简单提一句:如果方法名里有各种特殊字符呢?该怎么处理?如果方法名里有$,在啊JNI里等价的对应着_00024

然后就是参数的转换了。需要注意这个方法是不是重载方法,是不是静态方法。重载方法需要在JNI里体现区别,一般JNI方法都有两个固定的参数,一个是JNIEnv *env 还有一个是this指针,但this需要根据静态方法和非静态方法区分成jobject this 和 jclass this .因为静态方法只返回类本身jclass,而不是类的实例jobject。

至于传入的数据类型,我们可以看下面的表格,对照下表翻译,更详细的可以看jni.h文件,具体的JNI函数如何写,这里不详细讲解。

 


 

 

上述操作只是给JNI函数的壳,但里面的怎么写,还需要更加深入的了解Soot框架,我想挑几个典型的数据结构,简单介绍一下soot的使用

由于使用了值工厂设计模式(value Factory design pattern),主要包含Chain,Scene,SootClass,SootField,SootMethod,Body,Local,Trap,Unit,Type,Modifier,Value,Box等一一系列数据结构,都是被封装好的数据结构,每一种结构都提供了一系列的属性和方法,可以供用户来做细致的操作。

这套框架比较完备的地方不仅如此,它甚至允许你定义自己的转换规则,就类似于LLVM的PASS,或者JS的babel编译器里的插件的概念,soot里叫做pack


 

 

上图中的第一个字母代表被转换对象:s 代表Shimple, j 代表 Jimple, b 代表 Baf , g 代表 Grimp

第二个参数代表pack的角色:b表示创建body,t代表用户自定义转换,o代表优化,a代表注释。最后一个参数p就是pack的意思,是不是很简单呢。第二行里先经过用户自定义转化,再优化,再注释,是不是就能实现华为宣称的优化10%-20%呢?

soot将每一条指令都封装成类似抽象语法树(AST)样式的结构(不是真正的AST结构,只是类似),这棵语法树里包括了很多的“子节点”,soot还提供了对这棵“语法树”各个子节点的增删改查功能。只要你熟悉编译器前端,一定会快速适应这种操作方式。

经过对官方文档的阅读,相信你已经熟悉了jimple的语法,下面,如何将它转换成JNI?这里也是最核心的地方了,必须同时对JAVA和JNI大量经验且能深入了解上述两种语言特性的,并且熟悉安卓源码的开发人员才能胜任,但这并不是难如登天的

Soot框架下该如何定义自己的遍历转换规则呢?看一下官方文档给出来的例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public static void main(String[] args) {
  PackManager.v().getPack("jap").add(
      new Transform("jap.myTransform"new BodyTransformer() {
 
        protected void internalTransform(Body body, String phase, Map options) {
          for (Unit u : body.getUnits()) {
            System.out.println(u.getTags());
          }
        }
      }));
  Options.v().set_verbose(true);
  PhaseOptions.v().setPhaseOption("jap.npc""on");
  soot.Main.main(args);
}

按照这个格式就能定义自己的pack了,转换规则就在新建的BodyTransformer类里,入口点是internalTransform。

 

(四)Soot的几个关键数据结构

(1) SootMethod

Soot提供了大量的API,用于对Jimple指令做颗粒度更细的操作,在soot的API里,有专门用于存储method的数据结构,叫SootMethod。可以先从官方文档中看一下,SootMethod这个下面的方法和属性。sooMethod用于表示JAVA里的的方法,通过geName等方法可以快速获取

从上图可知,这个类里包含了方法所对应的很多属性和方法。但是由于仍然不直观,我们截取了调试窗口,查看这个数据结构:

观察可知其实,这里的SootMethod是<demo.have_a_try: void main(java.lang.String[])>,然后这个字符串又被分割成一个一个颗粒度更小的元素。比如SootMethod.getName获取方法名,main,调试窗口里也显示name属性的值是main

(2) unit

再看第二个数据结构unit:unit就是一条单独的jimple语句。老规矩先看文档:

具体包含哪些方法呢?如下:

我们可以这样使用:

 

1
Chain<Unit> units = b.getUnits();

 

我们使用b.getUnits 方法来获取一个包含了所有unit的数组,这里的b是指body,一个更上层的数据结构。b.getUnits结果会返回一个链表,包含了这个body里所有的unit,这个链表的数据结构Chain如下图所示

再接着

 

1
Iterator var4 = units.iterator();

接着使用迭代器来遍历每一个unit,即表示我们能拿到每一条jimple语句,可以进行转换了;

 

 

 

最后列举几个Unit对象关键API:

public List<ValueBox> getUseBoxes();//返回Unit中使用的Value的引用

public List<ValueBox> getDefBoxes();//返回Unit中定义的Value的引用

public List<ValueBox> getUseAndDefBox();//返回Unit中定义并使用的Value的引用

public List geUnitBoxes();//获得被这个unit跳转到的UnitxBox的List

public List getBoxesPointingTothis();//获得该unit作为跳转对象时,所有跳转本身的UnitBox

public boolean fallsThrough();//如果接下来执行后面挨着的unit,则为true

public boolean branches();//如果执行时会跳转到其他别的unit,则返回true。如:IfStmt、GotoStmt

public void rediectJumpsToThisTo(Unit newLocation);//该方法把跳转到该unit重定向到newLocation

其实soot就只有几种数据结构,上面的两个用到的比较多,也比较直观,剩下的种结构去慢慢熟悉就可以了。

 

 

(四)遍历与转换

 

只要是编译框架,一定会提供便遍历功能,比如JS的babel编译器里的traverse函数,每一种编译框架都有自己的遍历方法,soot里提供了一种十分方便的方法 apply,它可对每一个节点进行遍历,我们自定义的转换操作也是在这里面

首先是系统定义了一个接口类,如下图所示


 

 

到这你应该感到很眼熟了。之前说过,jimple代码是以15种stmt类型指令为主的,将200多种java字节码转化成近20种指令,已经大大简化了,而上面的15条指令正好对应着15种statement!

那么这个抽象接口是用来干嘛的?大家应该猜到了,是soot框架提供的,用于自定义pack的编写用的

如果我们重写这个抽象接口,就能定义自己的转换规则了!

 

当然

仅仅重写这一个接口是不够用的,还有一系列接口要重写,如下:

 

 

这些接口已经覆盖了上面那张Jimple语法表里全部的指令,重写里面的方法,以JImple指令: virtualinvoke $r1.<java.io.PrintStream: void println(java.lang.String)>("Hello World") 为例,调试器里表现为:

最终转换成

kanxue_CallVoidMethod(env, kanxue _locals[1],"java/io/PrintStream","println", "(Ljava/lang/String;)V", kanxue _s0)

 

这里由于比较敏感kanxue_CallVoidMethod函数的细节就不公开了。总之就是提前写好一些封装了调用逻辑的库函数,然后把参数从jimple语法中提取或者转译后,填入这个函数中,再从库里调用就可以了。


 

 

(五) 修改函数的名字并用NDK编译

最后只要把函数名改成native方法名之后,再编译成so即可。 过五一了,写的比较仓促,以后还会慢慢完善的。 我先去玩了。


 

 

结语

综上,通过soot的中间代码实现的优化和转义,是一种JavatoC的思路。其实这种方式不仅仅能提高执行速度,还可以提升安全性,所以还是挺有前景的技术,现在华为要开源,如果真走上述技术路线的话,对安全圈也是一大利好。

可以看到,这样我们就实现了了华为方舟编译器所宣传的所有功能项。其实从头到尾,我们一直使用别人的成熟的技术,即使是最关键的转化规则也可以通过时间积累,慢慢完善。编译器其实还是挺有意思的。

 


 

 

 

 


[培训]《安卓高级研修班(网课)》月薪三万计划

最后于  2019-5-9 13:43 被r0Cat编辑 ,原因:
 
 
 
 
收藏 
 
 
点赞4
 
 
打赏
 
 
分享
 
打赏 + 5.00雪花
打赏次数 1 雪花 + 5.00
收起 
赞赏  junkboy   +5.00 2019/05/02 感谢分享~
最新回复 (39)
 
luskyc  2019-5-2 17:10
 
  2 楼 
根据方舟编译器的官宣ppt,工程编译生成apk后,apk里面没有dex,只有so
 
junkboy  2019-5-2 17:21
 
  3 楼 
支持         
 
skyege   2 2019-5-2 18:49
 
  4 楼 
有技术前景
 
看场雪   3 2019-5-2 19:30
 
  5 楼 
如何验证方舟编译器在兼容性上的风险?
如果把这个编译器作用在安装apk的过程中,是否能有效降低兼容性风险?
 
xss   4 2019-5-2 20:48
 
  6 楼 
等emui9.1正式上线 erofs+方舟编译过的程序
 
飞天毛驴腿  2019-5-3 05:44
 
  7 楼 
关键字:llvm
 
无边  2019-5-3 05:57
 
  8 楼 
这么发展下去,加固厂商还能活下去吗?
 
luskyc  2019-5-3 21:28
 
  9 楼 
 无边 这么发展下去,加固厂商还能活下去吗?
加固厂商也得跟着转变思路
否则当然活不下去
 
葫芦娃   1 2019-5-4 00:10
 
  10 楼 

Java2JNI跟脱离虚拟机还是有很大差距的,Java Native Interface归根结底跑的还是虚拟机,除了运算指令,若是调用、变量读写等依然还是使用JNI,那所谓的性能提升其实很有限,执行时其实更多的可能是虚拟机对对象的操作令得耗时。

 

做了一个小测试,分别使用Java和JNI来调用一个Java方法十万次,结果是JNI的耗时的Java的十倍以上,而运算指令是native比Java快2-3倍,当然并不能说明什么,因为都同样是微秒级别,并且全编完应该是JNI调JNI了,而不是JNI调Java,那样速度应该会快不少。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
23:43:55.340  E/profile__: [static] java start: 1556898235326, end: 1556898235339, time: 13
23:43:55.443  E/profile__: [static] jni start: 1556898235343, end: 1556898235442, time: 99
23:44:04.287  E/profile__: [static] java start: 1556898244279, end: 1556898244287, time: 8
23:44:04.384  E/profile__: [static] jni start: 1556898244288, end: 1556898244383, time: 95
23:44:08.535  E/profile__: [static] java start: 1556898248528, end: 1556898248535, time: 7
23:44:08.636  E/profile__: [static] jni start: 1556898248536, end: 1556898248636, time: 100
23:44:10.917  E/profile__: [static] java start: 1556898250908, end: 1556898250917, time: 9
23:44:11.016  E/profile__: [static] jni start: 1556898250918, end: 1556898251015, time: 97
23:44:14.959  E/profile__: [static] java start: 1556898254950, end: 1556898254958, time: 8
 
23:51:12.300 E/profile__: [add] java start: 1556898672287, end: 1556898672299, time: 12
23:51:12.303 E/profile__: [add] jni start: 1556898672301, end: 1556898672303, time: 2
23:51:13.924 E/profile__: [add] java start: 1556898673916, end: 1556898673923, time: 7
23:51:13.926 E/profile__: [add] jni start: 1556898673924, end: 1556898673926, time: 2
23:51:16.125 E/profile__: [add] java start: 1556898676117, end: 1556898676124, time: 7
23:51:16.127 E/profile__: [add] jni start: 1556898676125, end: 1556898676127, time: 2
23:51:16.613 E/profile__: [add] java start: 1556898676607, end: 1556898676613, time: 6
23:51:16.615 E/profile__: [add] jni start: 1556898676613, end: 1556898676615, time: 2

不过总的来说,想要做Java2Native完全脱离虚拟机其实还是有很多东西需要去思考和解决,比如Java原生库(java.lang.*, Set, Map等)是否也需要2native编译一遍,还是按需编译,还是依然调用虚拟机,还是直接改Runtime。
再比如Java的一些特性,比如是否需要再搞一个自动回收?但如果编译器做的东西太多那就相当于做了个静态的解释器在代码里,只能白白增大体积,

 

华为公布的内容没怎么看,不过我猜这个可能是以编译器+Runtime的形式存在,现在看到的好像都是一些对公众公布的原理,没有说到具体细节,也不知道是否真的“完全脱离虚拟机”。

 

 

posted @ 2022-04-06 13:29  papering  阅读(142)  评论(0编辑  收藏  举报