1.查看统计当前目录下文件的个数
1 |
$ ls -l | grep "^-" | wc -l |
2.查看统计当前目录下文件的个数,包括子目录里的。
1 |
$ ls -lR| grep "^-" | wc -l |
3.查看某目录下文件夹(目录)的个数,包括子目录里的。
1 |
$ ls -lR| grep "^d" | wc -l |
1.查看统计当前目录下文件的个数
1 |
$ ls -l | grep "^-" | wc -l |
2.查看统计当前目录下文件的个数,包括子目录里的。
1 |
$ ls -lR| grep "^-" | wc -l |
3.查看某目录下文件夹(目录)的个数,包括子目录里的。
1 |
$ ls -lR| grep "^d" | wc -l |
有人留言希望尝试在WDMyCloud上尝试使用USB无线网卡,目标芯片是MT7601U。
经过几天的研究,找到了相关的编译方式。
链接地址 https://github.com/porjo/mt7601u,提示在Linux-4.2以后的版本中已经集成MT7601U芯片的驱动了(drivers/net/wireless/mediatek目录下),但是可惜的是WDMyCloud上的Linux内核版本号是3.2.26,曾经尝试升级到4.2版本之后的Linux内核,可惜尝试之后,发现无法成功编译Mindspeed C2000芯片(WDMyCloud使用的IC芯片,包含CPU,网卡等)驱动,因此只能退而求其次,使用MTK官方提供的驱动程序在3.2.26版本的Linux内核上进行编译。
另外注意,在WDMyCloud提供的默认系统镜像上,没有提供802.11相关的驱动,导致如果使用无线网卡,必须重新编译内核。
1.下载MT7601U芯片驱动
驱动程序的下载地址为:http://www.mediatek.com/products/broadbandWifi/mt7601u#product-downloads
也可本站下载
2.下载最新的WDMyCloud的系统源代码
目前最新的下载地址为:http://downloads.wdc.com/gpl/gpl-source-wd_my_cloud-04.04.03-113.zip
3.参照 How to successfully build packages for WD My Cloud from source构建编译环境
4.解压缩下载到的代码中的"packages/kernel_3.2"到"64k-wheezy/build/root"目录下面。
5.执行如下命令,重新编译内核,为驱动的编译准备必备的文件
1 2 3 4 5 6 7 8 9 10 11 |
$ cd ~ $ cd 64k-wheezy $ sudo chroot build $ cd root $ cd kernel_3.2 $ apt-get install uboot-mkimage |
修改内核编译文件"/kernel_3.2/linux/arch/arm/configs/sequoia64k-wifi_defconfig",在文件的顶部增加"CONFIG_WEXT_PRIV=y",定义这个宏的目的,是为了编译MT7601U芯片驱动的时候使用的,缺少这个宏会导致芯片驱动编译的时候缺少变量。
6.编译内核
1 |
$ make sequoia64k-wifi |
编译成功后,在"kernel_3.2/_bld"目录下生成编译文件,在"kernel_3.2/_bin"目录下生成最终的内核镜像和驱动程序。
7.解压缩MT7601U芯片驱动代码到"64k-wheezy/build/root"目录下面,并命名为"MT7601U"。
修改芯片驱动编译文件"Makefile",找到
1 2 3 4 |
ifeq ($(PLATFORM),IXP) LINUX_SRC = /project/stable/Gmtek/snapgear-uclibc/linux-2.6.x CROSS_COMPILE = arm-linux- endif |
在这行下面增加
1 2 3 4 5 6 7 8 9 |
ifeq ($(PLATFORM),WDMyCloud) # Linux 2.6 LINUX_SRC = /root/kernel_3.2/_bld # Linux 2.4 Change to your local setting #LINUX_SRC = /usr/src/linux-2.4 LINUX_SRC_MODULE = /root/kernel_3.2/kernel/linux/drivers/net/wireless/ CROSS_COMPILE = arm-linux-gnueabihf- #export ARCH=arm endif |
修改"Makefile"中,编译目标系统从PLATFORM = PC
为PLATFORM = WDMyCloud
.
接下来修改"os/linux/config.mk",在文件尾部增加
1 2 3 4 5 |
ifeq ($(PLATFORM),WDMyCloud) EXTRA_CFLAGS := -D__KERNEL__ -I$(LINUX_SRC)/include -Wall -Wstrict-prototypes -Wno-trigraphs -O2 -fno-strict-aliasing -fno-common -Uarm -fno-common -pipe -march=armv6 -msoft-float -Uarm -DMODULE -DMODVERSIONS -include $(LINUX_SRC)/include/linux/version.h $(WFLAGS) export EXTRA_CFLAGS endif |
最后,切换到驱动程序所在的"MT7601U"目录,执行编译。
1 |
$ make |
编译完成后,在"MT7601U/os/linux/"目录下生成名为"mt7601Usta.ko"的驱动程序文件。
剩下的,参考"MT7601U"目录下的"README_STA_usb"中的说明进行操作即可。
注意,如果要成功使用无线网卡驱动,那么需要用刚刚编译好的Linux内核,替换掉原来的内核,并且把"kernel_3.2/_bin"目录下的驱动也拷贝到系统根目录下面相同的位置即可,这个操作是高风险操作,极可能这么操作之后,系统无法正常运行,因此要提前备份文件,并且做好拆硬盘,挂载到其他机器上撤销刚刚的修改的准备。
1 2 3 4 5 6 |
$ pakfire update --force $ pakfire upgrade # pakfire 访问的服务器地址为 pakfire.ipfire.org 最近这个地址的访问不是非常稳定 # 如果发生失败,观察日志 cat /var/log/messages |
如果 HTTPS
可以正常访问,那么需要手工修改一下系统的代码,如下:
1 |
$ sudo vim /opt/pakfire/lib/functions.pl |
搜索找到如下内容:
1 |
$proto = "HTTP" unless $proto; |
替换为:
1 |
$proto = "HTTPS" unless $proto; |
这个属于客户端代码没有正确适配,同时服务器上的修改没有进行兼容导致的。
树莓派摄像头模块(Pi Cam)发售于2013年5月。其第一个发布版本配备了500万像素的传感器,通过排线链接树莓派上的CSI接口。而Pi Cam的第二个发布版本——也被叫做Pi NoIR中,配备了相同的传感器,但没有红外线过滤装置。因此第二版的摄像头模块就像安全监控摄像机一样,可以观测到近红外线的波长(700 - 1000 nm),不过当然同时也就牺牲了一定的显色性。
本文将会展示如何在树莓派上安装摄像头模块。 我们将使用第一版摄像头模块来演示。在安装完摄像头模块之后,你将会使用三个应用程序来访问这个模块:raspistill, raspiyuv 和raspivid。其中前两个应用用来捕捉图像,第三个应用来捕捉视频。raspistill 工具生成标准的图片文件,例如 .jpg 图像,而 raspiyuv 可以通过摄像头生成未处理的 raw 图像文件。
按照以下步骤来将树莓派摄像头模块连接搭配树莓派:
好了,现在你的 Pi Cam 已经准备就绪,可以拍摄照片或视频了。
在安装完摄像头模块之后,首先要确认你已经升级了树莓派系统并应用了最新的固件。可以输入以下命令来操作:
1 2 |
$ sudo apt-get update $ sudo apt-get upgrade |
运行树莓派配置工具来激活摄像头模块:
1 |
$ sudo raspi-config |
移动光标至菜单中的 "Enable Camera(启用摄像头)",将其设为Enable(启用状态)。完成之后重启树莓派。
安装完摄像头模块后的完成照:
在重启完树莓派后,我们就可以使用Pi Cam了。要用它来拍摄照片的话,可以从命令行运行raspistill:
1 |
$ raspistill -o keychain.jpg -t 2000 |
这句命令将在 2000ms 后拍摄一张照片,然后保存为 keychain.jpg。下面就是一张由 Pi Cam 拍摄的我的小熊公仔钥匙链。
raspiyuv 工具用法差不多,只不过拍摄得到的是一张未处理过的raw图像。
想要用摄像头模块拍一段视频的话,可以从命令行运行 raspivid 工具。下面这句命令会按照默认配置(长度5秒,分辨率1920x1080,比特率 17Mbps)拍摄一段视频。
1 |
$ raspivid -o mykeychain.h264 |
如果你想改变拍摄时长,只要通过 "-t" 选项来设置你想要的长度就行了(单位是毫秒)。
1 |
$ raspivid -o mykeychain.h264 -t 10000 |
使用 "-w" 和 "-h" 选项将分辨率降为 1280x720...
1 |
$ raspivid -o mykeychain.h264 -t 10000 -w 1280 -h 720 |
raspivid 的输出是一段未压缩的 H.264 视频流,而且这段视频不含声音。为了能被通常的视频播放器所播放,这个 raw 的 H.264 视频还需要转换。可以使用 gpac 包中所带有的 MP4Box 应用。
在 Raspbian 上安装 gpac,输入命令:
1 |
$ sudo apt-get install -y gpac |
然后将这段 raw 的 H.264 格式的视频流转换为每秒30帧的 .mp4 格式视频:
1 |
$ MP4Box -fps 30 -add keychain.h264 keychain.mp4 |
视频长度为10秒,使用默认分辨率以及比特率。
如果想要获取 raspistill, raspiyuv 和 raspivid 的完整命令行选项,不加任何选项直接运行以上命令即可。
在新版本的Android Studio
中开启混淆的方法如下:
1 2 3 4 5 6 7 |
buildTypes { release { minifyEnabled true //是否混淆 shrinkResources true //是否去除无效的资源文件 proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } } |
具体解释一下minifyEnabled
用来影响是不是开启混淆,shrinkResources
只有在minifyEnabled
为true
的情况下,才能有效,用来去除无效的资源文件。proguard-android-optimize.txt
是Android SDK
->tools\proguard
目录下,Google
已经写好默认的混淆模板文件,其中的proguard-android.txt
默认没有配置代码优化,而proguard-android-optimize.txt
默认配置了代码优化,至于我们自己工程下面的proguard-rules.pro
文件,只要配置我们自定义的额外配置即可,其他的用默认配置即可。
顺便讲一下代码混淆的好处:
1.代码安全,不易理解,增加破解难度。
2.减小APK的体积,减少内存开销。
3.缩减类名,方法名的长度,减少CPU开销。
Android
混淆代码时提示"Warning:org.apache.commons.httpclient.util.URIUtil: can't find referenced class org.apache.commons.codec.net.URLCodec
"
解决方法是proguard-rules.pro
中增加如下语句:
1 |
-dontwarn org.apache.commons.httpclient.** |
其他类似的警告信息,可以参考以上的方法,逐行添加即可。
ProGuard error can't find superclass or interface org.apache.http.entity
QFix 是手Q团队近期推出的一种新的 Android 热补丁方案,在不影响 App 运行时性能(无需插桩去 preverify)的前提下有效地规避了 dalvik 下”unexpected DEX”的异常,而且还是很轻量级的实现:只需调用一个很简单的方法就能办到。
自2015年 Android 热补丁技术开始出现,之后各种方案和框架层出不穷,原创性的技术方案主要有以下几种:
插桩的解决方案会影响到运行时性能的原因在于:app 内的所有类都预埋引用一个独立 dex 的空类,导致安装 dexopt 阶段的 preverify 失败,运行时将再次 verify+optimize。近期我们通过 ReDex 尝试优化手Q的启动性能时发现:
另外即使后期手Q的发布版本实际上无需发布补丁,我们也需要预埋插桩的逻辑,这本身也是不合理的一点,所以确实有必要去探索新的方向,既保留补丁的能力,同时去掉插桩带来的负面影响。
寻找新的解决方案,还是需要回过头来分析下这个异常出现的条件:
主要思路是:每当系统调用到这个方法,通过 native hook 拦截这个系统方法,更改这个方法的入口参数,将 fromUnverifiedConstant 统一改为 true,但和 Andfix 类似,native hook 方式存在各种兼容性和稳定性问题,而且拦截的是一个涉及 dalvik 基础功能同时调用很频繁的方法,无疑风险会大很多。
这段逻辑所在的方法是 dvmResolveClass,通过类之间的引用会调用这个方法,入口参数分别是引用类的 ClassObject,被引用类的 classIdx,以及引用关联的 dalvik 指令是否为 const-class/instance-of,返回的是被引用类的 ClassObject,经反复阅读分析,终于发现了一个可以利用的细节:
dvmResolveClass 在最开始会优先从当前 dex 已解析类的缓存里找被引用类,找到了直接返回,找不到时说明被引用类还没有被加载,接着加载成功后,会往当前 dex 缓存里设置上这个类的引用,后续所有对补丁类的解析引用都不会走到后面的“unexpected DEX”异常逻辑里,至于 dex 里已解析类 get/set 的相关逻辑如下:
另外考虑多 dex 的情况,补丁类很可能被多个不同 dex 里的类引用,那么需要在每个 dex 里找到一个引用类来预先引用补丁类吗?如果 app 里引用类和补丁类原本是在同一个 dex 里,引用类有可能是 preverify 的,这种情况是需要预先引用的;如果原本就不是一个 dex 里的,引用类由于有对其它 dex 类的依赖,就肯定不是 preverify 的,这种情况条件2本来就是不满足的,就没有必要预先引用了,所以可以推断出只需要针对补丁类在原先 App 所对应的 dex 进行预先引用即可。
梳理了思路后,马上在一个简单的 demo 上验证:
上面的 demo 预埋了补丁里包含的类,但在实际运用中我们是无法预先设定哪些类要打补丁的,dex 里对补丁类 const-class/instance-of 方式的引用指令是编译时确定的,但具体是哪些类又需要在运行时动态确定,所以这种动态方式行不通,最初想到的是类似插桩的做法,预先把 app 里所有类都以 const-class 方式引用一遍,但很明显有以下问题:
1)由于 App 里类的数量很多,所有类的预先引用统一放在一个地方肯定不现实,需要分散在多个区,只对补丁类所在的少数几个区执行预先引用的操作,但这里如何划分的粒度不好把握,而且 App 里的类及数量一直变化,我们做过一些尝试,但没有比较理想的可考量的方案。
2)预先引用解析所有类,会增加引用类的加载耗时和引用语句本身的执行耗时,对于执行耗时,可以通过添加条件判断来优化,如果要解析的类在补丁类名列表里就执行该语句,否则就不执行,对于加载耗时,初步的测试结果如下(这里一个划分的区包含500个左右的类,并进一步区分了是否 preverify,而测试的补丁包里包含2个类):
3)该方案实现起来特别繁琐,不实用。
新的方案在 Java 层找不到可行的实现方式,就尝试从 native 层切入,只需首次引用解析补丁类时,直接通过 jni 调用 dalvik 的 dvmResolveClass 这个方法,当然传入的参数 fromUnverifiedConstant 需要设为 true,这个思路与前面说的 native hook 方式不同,不会去 hook 这个系统方法,而是从 native 层直接调用:
这里的关键是能获取到前两个参数的值,第一个参数引用类的 ClassObject,最初借鉴的是 dvmResolveClass 里调用的 dvmFindClassNoInit 这个方法,但这个方法获取一个类的 ClassObject 需要两个参数,其中类名很容易构造,但需要额外的操作获取引用类的 ClassLoader 对象的地址,之后又找到一个更便利的方法 dvmFindLoadedClass:
输入: 原有 apk 的所有 dex、补丁包所有的类名
输出: 补丁包每个类所在 dex 的编号以及 classIdx 的值
注1: 如果在补丁新增原 app 不存在的类,运行时新增类只会被补丁 dex 即同一个 dex 里的类所引用,所以新增的补丁类无需预先解析引用。
注2: 由于”unexpected DEX”异常出现在 dalvik 的实现里,art 模式下不会存在,以上预先引用补丁类的逻辑只需用在5.0以下的系统。
最终新方案的整体实现流程如下图所示:
这个方案由于是 native 层的,我们也通过众测方式对兼容性做了充分的验证:
1. 不同系统版本导出符号:
在2.x版本dalvik是用C写的,2.3以上的4.x版本是用C++写的,基于C++ name mangling原理, dvmFindLoadedClass在编译后会变为_Z18dvmFindLoadedClassPKc,但经IDA反汇编libdvm.so分析,dvmResolveClass没有变化
2. yunos ROM的兼容性问题:
在第一次众测任务中,有446位用户参与,其中有6位反馈补丁不生效的问题,从反馈的结果码看都是libdvm.so加载成功,但是符号导出为NULL导致的,后来发现这6位用户安装的都是yunos的rom,经分析定位到原因如下:
可以看到dlopen libdvm.so时将库的名字改为了libvmkid_lemur.so,yunos的dalvik实现实际上在后面这个库里,而且通过反汇编发现导出的符号名也变化了,但内部的实现逻辑没有变化:
1 2 |
dvmResolveClass -> vResolveClass _Z18dvmFindLoadedClassPKc -> _Z18kvmFindLoadedClassPKc |
在dlsym调用时考虑以上两种可能的符号名即可,经本地和以上问题用户的再次验证,已成功解决。
3. x86平台的兼容性问题:
解决了yunos的兼容问题后,在第二次众测任务中,有1884位用户参与,有3位反馈异常,发现问题用户都是x86平台的,由于最开始未对x86平台作兼容,arm平台的动态库在x86手机上运行的异常有两种:
a) 部分手机一直卡在黑屏界面,经日志定位,这些手机都安装了houndini的第三方库,会自动将arm的so转换为x86平台兼容的,so加载及符号导出都没问题,在成功获取dvmResolveClass符号地址后,就一直卡在dvmResolveClass的调用逻辑里,应该是houndini库的转换问题
b) 部分手机运行正常,但导出符号都为NULL
在提供x86平台的so后,以上两个问题也成功解决了。
本文探讨的主要是为解决补丁 Java 方案在 dalvik 下”unexpected DEX”异常提供一个新的思路,在整个 Android 补丁大的技术框架下,只是其中一个环节,有问题,欢迎大家多多交流!
Linux
下的so
文件通常是作为动态链接库使用的,但其实so
文件跟可执行程序一样都是ELF
格式,所以应该都是可以直接执行的。
Linux
下编译可以执行的so
文件如下:
1 2 3 4 5 6 7 8 |
#include <stdio.h> #include <stdlib.h> void lib_entry() { printf("Entry point of the service library\n"); exit(0); } |
注意,lib_entry()
必须以exit(0)
结束,否则会导致进程退出失败。
使用如下命令编译源代码:
1 |
$ gcc -shared service.c -o libservice.so -Wl,-e,lib_entry -fPIC |
-Wl
表示传递给链接器ld
的参数,分隔的逗号会被替换成空格。-e,lib_entry
就指明了入口函数。
而对于Android
来说,只需要在Android.mk
文件中增加LOCAL_LDLIBS += -Wl,-e,lib_entry
就可以达到相同的目的了。
int main(int argc,char* argv[])
这样的参数进去?答案是不能,那么启动参数从哪里读取呢? 答案就是从/proc/$pid/cmdline
中手工解析获取。
C
库的代码,在调用代码时候为什么没有崩溃?正常情况下,可执行程序的入口函数实际上是C
库的的入口函数,然后C库自身初始化完成,解析参数后调用我们自己实现的入口函数。按照常规逻辑,如果没有初始化C
库,那么调用C
库函数的时候,几乎肯定是会崩溃的。
反编译正常的可执行程序,应该都能看到C库的初始化函数,而指定了入口的,基本上都没有这个函数的调用。
但是在Linux
下面ld.so
会帮我们初始化一次C
库,而我们又是被ld.so
加载起来的,因此理论上,我们不需要再次初始化C
库了。
Android
系统上使用SQLite
数据库存储数据,结果发现,如果刚刚写入数据之后在很短的时间之内,如果立即断电会丢失刚刚写入的数据。
根据Google
官方的文档,发现,从API-16
开始,提供了enableWriteAheadLogging
这个API
来要求SQLite
先写日志,后写数据库。这个行为才是常规数据库默认的行为。
一般Android
设备使用的存储设备都是Flash
闪存,是有写入寿命以及空间限制的,因此默认不启用日志功能,也是迫不得已,更何况数据库日志属于只增不减的,这就导致长时间运行后,会出现空间无法释放的问题。
还有一个解决方法就是,插入以及修改数据的时候,启用SQLite
的事务模型,由于事务一定要保证数据已经同步到磁盘了,因此,可以避免出现断电后数据由于没有刷新到磁盘导致的数据丢失。
1 2 3 4 5 6 7 |
db.beginTransaction(); try { ... db.setTransactionSuccessful(); } finally { db.endTransaction(); } |
很多时候,会发现直接通过Kill
,杀掉进程,一般是不会丢失数据的,原因在于磁盘写入的时候,系统会进行缓存,等合并到一定的量或者时间,系统一次性同步到磁盘,这样可以大大提供系统的性能。因此进程虽然已经死掉了,但是系统还是会把已经提交到内核的数据刷新到磁盘的,因此表现就是数据不会丢失。但是如果是断电的话,系统也就无能为力了。于是表现就是,越是新的Linux
内核版本,反倒越是在异常断电的时候容易丢失数据。