java readint 大端格式_记一次诡异的性能bug

虽然说Android 10 patchoat已不再使用,但是仍想把这个事记录下,能发现原生Android art问题也是件有趣的事,本身发现问题,解决问题过程中,也可以学到一些新东西。

最后呢,还是应证了那句话,所有诡异的问题到最后都是简单的bug,如果到最后列入了玄学,那多半是你不够深入。

起因

[基于Android 8.1平台 相关代码等已做脱敏处理]

事情的背景是这样的,平台有几个应用从点击然后会黑3s,然后才显示出来(被杀了,然后再启动该应用也这样慢),虽然这个应用有3D一些东西但这个启动速度肯定是无法让人接受的,

那咋办,解决呗!

那…咋解决(优化)? 是从cpu, io, memory,调度等角度入手吗?

还好,用同一平台别的项目机器发现不存在该问题,然后回退该项目的版本,早期版本是好的,最终定位到某个patch,

然而,诡异的是,该同事提交的接口没人用,是hardware下的HWBinder相关的服务(android.hardware.noname-V1.0-java),和该应用也八杆了都打不着,

但是确确实实回退该代码就好了。

那咋搞了呢?代码肯定最终是要要的——尽管现在没人调用,那还是得从根上找找原因。

这个时候性能的同事也上场了,发现那会儿cpu占用老高了,还有一些JIT相关的啥啥啥……,这个时候呢,从性能找原因也是个方向,说不定能成长为大牛,替掉art,干掉安卓,某果啥的性能都是小菜……(想多了)

事情的转机在测试的同事报了个patchoat相关的 signal 6 (SIGABRT)fault addr 问题,

然后好的版本是没问题的,进一步发现是 /data/dalvik-cache/arm 没有生成(那个应用是32位的),而/data/dalvik-cache/arm64有生成,在正常的版本上/data/dalvik-cache/arm都是好的,我们知道这个东西也是谷歌为了性能优化搞的一些东东,所以现在呢,

调查方向由性能调查转为了dalvik cache没有生成,也就是patchoat为啥crash了。性能 -> dalvik cache -> patchoat crash

同时呢,也有了问题1: 别人的HW Binder服务都好好的,为啥该服务(android.hardware.noname-V1.0-java)还与dalvik-cach这些扯上关系了?那是因为android.hardware.noname-V1.0-java加到了PRODUCT_BOOT_JARS里,然后呢会生成boot...oat之等东西,

为了加速性能和安全,开机后patchoat会根据/system/framework/{arch}/下boot.art和其他boot-*进行重定位

可看下我平台下的这些boot*文件

$ ls /system/framework/arm/

......

boot-android.hardware.noname-V1.0-java.art

boot-android.hardware.noname-V1.0-java.oat

boot-android.hardware.noname-V1.0-java.vdex

boot-framework.art

boot-framework.oat

boot-framework.vdex

......

boot.art

boot.oat

boot.vdex

然后另外一同事经过一翻斗争,提出了如下的代码解决了INoNameHwDevice.hal

- setTestVall(int32_t value) generates (Result setVolumeRet);

+ setTestVall(int32_t value) generates (Result setTestVallRet);

可是为啥会解决问题呢?猜测是因为和另外一个函数返回值一样setVolume(TestChannelType type, int32_t value) generates (Result setVolumeRet);

啥?HW Binder服务两个函数返回值还不能一样?这个bug的确是有些诡异…那是不是这么回事呢?

作为一好奇宝宝,我觉得吧,太诡异了,得研究研究看为啥,是不是HWBinder实现带的原生坑,如果是这样的话以后好避坑。

反正还有点空闲,那就看看吧,然后我就查看了HW Binder工具HIDL生成的代码(HIDL和AIDL差不多,都是谷歌用来帮生成进程间通信代码的一个工具,只是HIDL是更底层的一些服务,也会生成C++的),INoNameHwDevice.hal -> INoNameHwDevice.java

out/target/common/gen/JAVA_LIBRARIES/....../INoNameHwDevice.java

@Override

public void onTransact(int _hidl_code, android.os.HwParcel _hidl_, final android.os.HwParcel _hidl_reply, int _hidl_flags)

throws android.os.RemoteException {

......

case 6 /* setVolume */:

{

_hidl_request.enforceInterface(evice.kInterfaceName);

int type = _hidl_request.readInt32();

int value = _hidl_request.readInt32();

int _hidl_out_setVolumeRet = setVolume(type, value); // 返回值 _hidl_out_setVolumeRet

_hidl_reply.writeStatus(.os.HwParcel.STATUS_SUCCESS);

_hidl_reply.writeInt32(_hidl_out_setVolumeRet);

_hidl_reply.send();

break;

}

......

case 8 /* setTestVall */:

{

_hidl_request.enforceInterface(evice.kInterfaceName);

int value = _hidl_request.readInt32();

int _hidl_out_setVolumeRet = setTestVall(value); // 返回值 _hidl_out_setVolumeRet

_hidl_reply.writeStatus(.os.HwParcel.STATUS_SUCCESS);

_hidl_reply.writeInt32(_hidl_out_setVolumeRet);

_hidl_reply.send();

break;

}

看了代码,好像说这是这两个返回值一样导致的好像也有可能(JAVA里同一个函数下不允许有相同名的多个变量,即使是他们的作用域不同,c/c++里是允许的),在java代码里一个函数有两个同名变量会编译出错,可是HIDL居然没报错,正常生成了代码,还能正常运行,这实在是另人费解。

而且,

前面提到回退另一同事的patch也能修复该问题,回退后生成的代码也是两个同名变量,这似乎又不能解释通,为啥两个同名变量一个能工作一个又不能工作。

我一度认为回退的patch是不是回退错了,多回退了,但是我自己回退也确定是能工作的。

我也不知道同事是如何定位到该问题,然后咋就想到这,然后偏偏又修复好了该问题,那就实战一把吧,自己动手查查该问题。

经过

分析patchoat crash分析

从logcat里,我们提取如下一些信息// 运行的命令, 注意input-image-location和instruction-set, 该命令意思是会根据/system/framework/arm/boot.art信息,重定位Image, 成功后输出在/data/dalvik-cache/arm/目录下

01-01 09:39:10.148 500 500 I zygote : RelocateImage: /system/bin/patchoat --input-image-location=/system/framework/boot.art --output-image-file=/data/dalvik-cache/arm/system@framework@boot.art --instruction-set=arm --base-offset-delta=-14499840

// crash信息

01-01 09:39:10.174 540 540 W patchoat: Could not reserve sentinel fault page

01-01 09:39:10.716 540 540 F patchoat: hash_set.h:218] Check failed: num_elements_ <= num_buckets_ (num_elements_=134217728, num_buckets_=0)

01-01 09:39:10.730 540 540 F patchoat: runtime.cc:523] Runtime aborting...

01-01 09:39:10.730 540 540 F patchoat: runtime.cc:523] Dumping all threads without appropriate locks held: thread list lock

01-01 09:39:10.730 540 540 F patchoat: runtime.cc:523] All threads:

..... 一些dump信息,略

第一个信息是运行的patchoat完整命令,也可以在手机上试试看能正常生成dalvik-cache不,直接用该命令就不用编ROM,刷机了,也可以加快调试。

第二个信息是从crash dump里能看出在哪儿挂了,及原因说明(num_elements_ <= num_buckets_)hash_set.h:218] Check failed: num_elements_ <= num_buckets_ (num_elements_=134217728, num_buckets_=0)

从crash信息里能看出大体的调用关系,要想具体定位某一行,可以根据关系分析,也可借助工具stack

该工具源码位置在 development/scripts/stack ,

用之前需要 编译下对应的相关代码,也可全编译整个源码,

使用方法就是运行该工具,然后把信息粘贴下,然后再按 Ctrl+Shift+d,

然后该工具就会给你分析出结果。更多c/c++ bug定位方法,可参看

https://blog.csdn.net/qq_33750826/article/details/98872326

示例(堆栈不重要,可略过,只是为了说下stack工具):$ development/scripts/stack

Reading native crash info from stdin

01-01 08:09:57.659 537 537 F libc : Fatal signal 6 (SIGABRT), code -6 in tid 537 (main), pid 537 (main)

01-01 08:09:57.714 545 545 I crash_dump32: obtaining output fd from tombstoned, type: kDebuggerdTombstone

01-01 08:09:57.715 545 545 E libc : failed to connect to tombstoned: No such file or directory

01-01 08:09:57.715 545 545 I crash_dump32: performing dump of process 537 (target tid = 537)

01-01 08:09:57.715 545 545 F DEBUG : *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***

01-01 08:09:57.715 545 545 F DEBUG : Build fingerprint: 'Android/.../...:8.1.0/.../941:userdebug/test-keys'

01-01 08:09:57.715 545 545 F DEBUG : Revision: '0'

01-01 08:09:57.715 545 545 F DEBUG : ABI: 'arm'

01-01 08:09:57.715 545 545 F DEBUG : pid: 537, tid: 537, name: main >>> /system/bin/patchoat <<<

01-01 08:09:57.715 545 545 F DEBUG : signal 6 (SIGABRT), code -6 (SI_TKILL), fault addr --------

01-01 08:09:57.718 545 545 F DEBUG : Abort message: 'hash_set.h:218] Check failed: num_elements_ <= num_buckets_ (num_elements_=134217728, num_buckets_=0) '

01-01 08:09:57.718 545 545 F DEBUG : r0 00000000 r1 00000219 r2 00000006 r3 00000008

01-01 08:09:57.718 545 545 F DEBUG : r4 00000219 r5 00000219 r6 ffe65064 r7 0000010c

01-01 08:09:57.718 545 545 F DEBUG : r8 00000000 r9 ef2f52f8 sl ef2f530c fp ef2f531c

01-01 08:09:57.718 545 545 F DEBUG : ip 00000002 sp ffe65050 lr ef07dc7b pc ef0776ac cpsr 200f0030

01-01 08:09:57.726 545 545 F DEBUG :

01-01 08:09:57.726 545 545 F DEBUG : backtrace:

01-01 08:09:57.726 545 545 F DEBUG : #00 pc 0001a6ac /system/lib/libc.so (abort+63)

01-01 08:09:57.726 545 545 F DEBUG : #01 pc 0035fdaf /system/lib/libart.so (art::Runtime::Abort(char const*)+262)

01-01 08:09:57.726 545 545 F DEBUG : #02 pc 00007d8d /system/lib/libbase.so (android::base::LogMessage::~LogMessage()+452)

01-01 08:09:57.727 545 545 F DEBUG : #03 pc 001e0291 /system/lib/libart.so (art::HashSet<:gcroot>, art::InternTable::GcRootEmptyFn, art::InternTable::StringHashEquals, art::InternTable::StringHashEquals, std::__1::allocator<:gcroot>>>::HashSet(unsigned char const*, bool, unsigned int*)+332)

01-01 08:09:57.727 545 545 F DEBUG : #04 pc 001dfcd9 /system/lib/libart.so (art::InternTable::Table::AddTableFromMemory(unsigned char const*)+28)

01-01 08:09:57.727 545 545 F DEBUG : #05 pc 001dfc9f /system/lib/libart.so (art::InternTable::AddTableFromMemory(unsigned char const*)+50)

01-01 08:09:57.727 545 545 F DEBUG : #06 pc 00005145 /system/bin/patchoat (art::PatchOat::PatchImage(bool)+180)

01-01 08:09:57.727 545 545 F DEBUG : #07 pc 000042df /system/bin/patchoat (art::PatchOat::Patch(std::__1::basic_string, std::__1::allocator> const&, long, std::__1::basic_string, std::__1::allocator> const&, art::InstructionSet, art::TimingLogger*)+4610)

01-01 08:09:57.727 545 545 F DEBUG : #08 pc 000072d3 /system/bin/patchoat (main+730)

01-01 08:09:57.727 545 545 F DEBUG : #09 pc 0007a435 /system/lib/libc.so (__libc_init+48)

01-01 08:09:57.727 545 545 F DEBUG : #10 pc 00002ffc /system/bin/patchoat (_start_main+88)

// Ctrl+Shift+d后输出,路径做了脱敏处理

Reading symbols from /home/.../out/target/product/.../symbols

signal 6 (SIGABRT), code -6 in tid 537 (main), pid 537 (main)

Revision: '0'

pid: 537, tid: 537, name: main >>> /system/bin/patchoat <<<

signal 6 (SIGABRT), code -6 (SI_TKILL), fault addr --------

Abort message: 'hash_set.h:218] Check failed: num_elements_ <= num_buckets_ (num_elements_=134217728, num_buckets_=0) '

r0 00000000 r1 00000219 r2 00000006 r3 00000008

r4 00000219 r5 00000219 r6 ffe65064 r7 0000010c

r8 00000000 r9 ef2f52f8 sl ef2f530c fp ef2f531c

ip 00000002 sp ffe65050 lr ef07dc7b pc ef0776ac cpsr 200f0030

Using arm toolchain from: /home/.../prebuilts/gcc/linux-x86/arm/arm-linux-androideabi-4.9/bin/

// 具体输出的代码行看右边FILE:LINE就行了

Stack Trace:

RELADDR FUNCTION ...... FILE:LINE

v------> inline_tgkill(int, int, int) ...... bionic/libc/bionic/abort.cpp:?

0001a6ac abort+64 ...... bionic/libc/bionic/abort.cpp:68

0035fdaf art::Runtime::Abort(char const*)+262 ...... art/runtime/runtime.cc:553

v------> std::__1::function::operator()(char const*) const ...... external/libcxx/include/functional:1915

00007d8d android::base::LogMessage::~LogMessage()+452 ...... system/core/base/logging.cpp:433

001e0291 art::HashSet<:gcroot>, art::InternTable::GcRoot......d int*)+332 art/runtime/base/hash_set.h:218 //

001dfcd9 art::InternTable::Table::AddTableFromMemory(unsigned char const*)+28 ...... art/runtime/intern_table.cc:368

v------> art::InternTable::AddTableFromMemoryLocked(unsigned char const*) ...... art/runtime/intern_table.cc:312

001dfc9f art::InternTable::AddTableFromMemory(unsigned char const*)+50 ...... art/runtime/intern_table.cc:308

v------> art::PatchOat::PatchInternedStrings(art::ImageHeader const*) ...... art/patchoat/patchoat.cc:476

00005145 art::PatchOat::PatchImage(bool)+180 ...... art/patchoat/patchoat.cc:600

000042df art::PatchOat::Patch(std::__1::basic_string

v------> art::patchoat_image(art::TimingLogger&, art::InstructionSet, std::__1::......, bool, bool) art/patchoat/patchoat.cc:781

v------> art::patchoat(int, char**) ...... art/patchoat/patchoat.cc:853

000072d3 main+730 ...... art/patchoat/patchoat.cc:872

0007a435 __libc_init+48 ...... bionic/libc/bionic/libc_init_dynamic.cpp:126

00002ffc _start_main+88 ...... external/libunwind_llvm/src/libunwind.cpp:?

根据调用堆栈信息和出错信息,整理的代码调用如下,main()/(art/patchoat/patchoat.cc)

+ art::patchoat(argc, argv);

+ patchoat_image()

+ PatchOat::Patch()

+ p.PatchImage(i == 0)

+ PatchArtFields(image_header);

| PatchArtMethods(image_header);

| PatchImTables(image_header);

| PatchImtConflictTables(image_header);

| PatchInternedStrings(image_header);

| + const auto& section = image_header->GetImageSection(ImageHeader::kSectionInternedStrings);

| + temp_table.AddTableFromMemory(image_->Begin() + section.Offset());

| + InternTable::Table::AddTableFromMemory() (art/runtime/intern_table.cc)

| + UnorderedSet set(ptr, /*make copy*/false, &read_count);

| + HashSet(const uint8_t* ptr, bool make_copy_of_data, size_t* read_count) (art/runtime/base/hash_set.h)

| uint64_t temp;

| size_t offset = 0;

| offset = ReadFromBytes(ptr, offset, &temp); // 从ptr, offset为0开始读64位,即8字节

| num_elements_ = static_cast(temp); // 读出值给num_elements_

| offset = ReadFromBytes(ptr, offset, &temp); // 再读64位

| num_buckets_ = static_cast(temp); // 给 num_buckets_

| CHECK_LE(num_elements_, num_buckets_); //

+ PatchClassTable(image_header);

从代码分析,HashSet()构造时从某个地址里读了两个8字节的数,结果这两数有问题(num_elements_=134217728, num_buckets_=0),那就继续沿着这两个数调查,

这个时候我调查又走了弯路,我去调查 为什么 num_buckets_ 为零,而不是比 134217728(即0x8000000)大的数,这两个数代表的是啥意思?

然后呢看 PatchInternedStrings(image_header) --> image_header->GetImageSection(ImageHeader::kSectionInternedStrings);

时,看到 kSectionInternedStrings 定义处有几个相关的定义art/runtime/image.h

enum ImageSections {

kSectionObjects,

kSectionArtFields,

kSectionArtMethods,

kSectionRuntimeMethods,

kSectionImTables,

kSectionIMTConflictTables,

kSectionDexCacheArrays,

kSectionInternedStrings,

kSectionClassTable,

kSectionImageBitmap,

kSectionCount, // Number of elements in enum.

};

觉得应该是ELF或者art格式的段之类相关的,又去查了下ELF或者art相关的东西(几年前看了ELF相关的,现在都忘了; art格式布局也没找到太好的资料,当然也可以从代码里整理出,但是我懒得整理)....

我也怀疑是不是编译生成的InternedStrings因为啥原因导致数出错了,一度怀疑是不是得回头把编译原理捡起来看看……调查方向:

性能 -> dalvik cache -> patchoat crash -> num_elements_ <= num_buckets_ -> 为啥num_buckets_不是大于0x8000000的数 -> ELF/art格式 -> InternedStrings -> 编译原理?

总之呢,走了一些弯路,加了好些log, 发觉事情也不对,走下去就是个死胡同了,我突然想看看 OK情况下这两个数为多少 才让事情有了转机,

这些弯路里虽然也学了新东西,我觉得最有用的还是发觉修改了

android.hardware.noname-V1.0-java后只需要push而且是全部push boot*后就可生效,这样也可以加快实验,节约时间。push boot*

adb push out/target/product/.../system/framework/arm/boot* /system/framework/arm/

让我们回到正常情况下num_elements_和num_buckets_为多少继续说,看加的log里,这两个数都为0,

那么这时候应该猜测,不是写入的num_elements_和num_buckets_有问题,而是 有可能读的时候,读到的是后面段地址的数据,

而 后面地址的数据是谁的数据呢?这个数据是本来就是0x8000000还是读的时候指针飞了?

因为每次出错都是0x8000000,所以读飞了情况不太可能,那就看看是不是重定位写入了脏数据还是原始的文件就是这样,

在这之前,我们得搞清楚是重定位 哪个文件,哪个地址时出问题了,这中间的一些过程和弯路导致数据对不上重复看代码的过程就不说了,

简单说下就是

/system/bin/patchoat --input-image-location=/system/framework/boot.art

运行时会根据boot.art里信息,然后挨个处理boot-*, 最终定位到确实是我们的HWBinder服务有问题,对应的文件名为

/system/framework/arm/boot-android.hardware.noname-V1.0-java.art

简化的流程为main()/(art/patchoat/patchoat.cc)

+ art::patchoat(argc, argv);

+ patchoat_image()

+ PatchOat::Patch()

+ for (size_t i = 0; i < spaces.size(); ++i) { //

| p.PatchImage(i == 0)

| + PatchInternedStrings(image_header);

| ......

| CHECK_LE(num_elements_, num_buckets_); //

+ }

具体代码为:art/patchoat/patchoat.cc

bool PatchOat::Patch(const std::string& image_location,

off_t delta,

const std::string& output_directory,

InstructionSet isa,

TimingLogger* timings) {

......

// 得到BootImage Spaces

std::vector<:space::imagespace> spaces = Runtime::Current()->GetHeap()->GetBootImageSpaces();

........

// Symlink PIC oat and vdex files and patch the image spaces in memory.

// 挨个处理

for (size_t i = 0; i < spaces.size(); ++i) {

gc::space::ImageSpace* space = spaces[i]; // space

std::string input_image_filename = space->GetImageFilename(); //

std::string input_vdex_filename =

ImageHeader::GetVdexLocationFromImageLocation(input_image_filename);

std::string input_oat_filename =

ImageHeader::GetOatLocationFromImageLocation(input_image_filename);

std::unique_ptr input_oat_file(OS::OpenFileForReading(input_oat_filename.c_str()));

......// 添加的log

+ LOG(ERROR) << "testlog open input image file at " << input_image_filename;

+ LOG(ERROR) << "testlog open input oat file at " << input_oat_filename;

......

PatchOat& p = space_to_patchoat_map.emplace(space, //

PatchOat(

isa,

space_to_memmap_map.find(space)->second.get(),

space->GetLiveBitmap(),

space->GetMemMap(),

delta,

&space_to_memmap_map,

timings)).first->second;

......

if (!p.PatchImage(i == 0)) {

void PatchOat::PatchInternedStrings(const ImageHeader* image_header) {

const auto& section = image_header->GetImageSection(ImageHeader::kSectionInternedStrings);

......

// 添加的log

+ PLOG(ERROR) << "testlog Begain:" << std::hex << image_->Begin() << " Offset:" << section.Offset();

temp_table.AddTableFromMemory(image_->Begin() + section.Offset());

日志输出:// 文件名

01-01 12:35:04.539 539 539 E patchoat: testlog open input image file at /system/framework/arm/boot-android.hardware.noname-V1.0-java.art

01-01 12:35:04.539 539 539 E patchoat: testlog open input oat file at /system/framework/arm/boot-android.hardware.noname-V1.0-java.oat

01-01 13:46:18.520 539 539 E patchoat: testlog Begain:art

// 偏移0x2000

01-01 13:46:18.520 539 539 E patchoat: 046 Offset:2000: No such file or directory

01-01 12:35:04.541 539 539 E patchoat: testlog elements 134217728 bukets 0

肯定了文件(boot-android.hardware.noname-V1.0-java.art)和位置(0x2000)之后,就应该看是本来文件数据就有问题,还是重定位写入了脏数据。调查方向:

性能 -> dalvik cache -> patchoat crash -> num_elements_ <= num_buckets_ -> 为啥num_elements_不为0 -> 确定文件和偏移 -> 文件本身问题?脏数据?

这时候就需要两个工具了oatdump和 二进制查看工具 vim -boatdump

这个工具就是用于dump art oat文件信息的,方便查看(对于dex格式的可用dexdump),

这个工具电脑或手机侧都可以用,因为我的代码都编译过,所以在电脑上使用快些也方便,

具体的手法可以

oatdump -h 查看下$ oatdump -h

Usage: oatdump [options] ...

Example: oatdump --image=$ANDROID_PRODUCT_OUT/system/framework/boot.art

Example: adb shell oatdump --image=/system/framework/boot.art

--oat-file=: specifies an input oat filename.

Example: --oat-file=/system/framework/boot.oat

--image=: specifies an input image location.

Example: --image=/system/framework/boot.art

.....

要说的 oatdump使用的坑

1.

就是后面接的路径.../system/framework/boot.art,得结合--instruction-set,所以

.../system/framework/boot.art 最终会找.../system/framework/arm/boot.art

2.

不知道为啥参数不能直接用boot-android.hardware.noname-V1.0-java.art,似乎只能是boot.art

,所以呢导致速度慢。

相关的dump信息如下,

从中可以看到 InternedStrings ClassTable ImageBitmap 三个的起始地址都是重叠的oatdump --image=$ANDROID_PRODUCT_OUT/system/framework/boot.art --instruction-set=arm

IMAGE LOCATION: /home/.../out/target/product/.../system/framework/boot-android.hardware.noname-V1.0-java.art

IMAGE BEGIN: 0x70810000

IMAGE SIZE: 8192

......

IMAGE SECTION SectionDexCacheArrays: size=7880 range=312-8192

IMAGE SECTION SectionInternedStrings: size=0 range=8192-8192 //这是我们要关心的段信息,range 8192即为0x2000

IMAGE SECTION SectionClassTable: size=0 range=8192-8192 // 注意和ClassTable重叠了

IMAGE SECTION SectionImageBitmap: size=4096 range=8192-12288 // 注意和ImageBitmap起始地址也是重叠的

OAT CHECKSUM: 0xfc8b0837

因为dump一次太慢了,所以对工具进行了修改,只需要dump文件名和Sections就行。

修改代码如下:art/oatdump/oatdump.cc

static int DumpImage(gc::space::ImageSpace* image_space,

fprintf(stderr, "Invalid image header %s\n", image_space->GetImageLocation().c_str());

return EXIT_FAILURE;

}

+ fprintf(stderr, "testlog ---->\n");

+ image_space->Dump(*os); // 这个会打出文件名

+ fprintf(stderr, "\n");

+ image_space->DumpSections(*os); // 打印出Section信息

+ fprintf(stderr, "testlog

+

+ bool test = false;

ImageDumper image_dumper(os, *image_space, image_header, options);

- if (!image_dumper.Dump()) {

+ if (test && !image_dumper.Dump()) { // 后面就不再dump很具体的信息了

return EXIT_FAILURE;

}

用修改后的工具dump出来信息格式不太一样,但是也表明

InternedStrings ClassTable ImageBitmap 三个的起始地址都是重叠的// 文件名在name里

SpaceTypeImageSpace begin=0x70810000,end=0x70810138,size=312B,name="/home/.../out/target/product/.../system/framework/arm/boot-android.hardware.noname-V1.0-java.art"]

SectionObjects 0x70810000-0x70810138

......

SectionIMTConflictTables 0x70810138-0x70810138

SectionDexCacheArrays 0x70810138-0x70812000

SectionInternedStrings 0x70812000-0x70812000 // 偏移即为0x70812000-0x70810000 = 0x2000

SectionClassTable 0x70812000-0x70812000 // 偏移也为0x2000

SectionImageBitmap 0x70812000-0x70813000 // 偏移也为0x2000

那接下来就用vim看下0x2000偏移里值为多少吧。vim -b

因为我的是Linux系统,所以用vim方便,你也可以用别的二进制查看工具

用法就是

vim -b 文件名

然后输入 %!xxd 就可以查看了$ vim -b boot-android.hardware.noname-V1.0-java.art

%!xxd

00000000: 6172 740a 3034 3600 0000 8170 0020 0000 art.046....p. ..

......

// 0x2000偏移的值

00002000: 0000 0008 0000 0000 0000 0000 0000 0000 ................

我们终于看到 0x2000~0x2007 这8个字节的值为 0000 0008 0000 0000 0000

还记得之前代码里我们从该地址读了8字节赋给了num_elements_吗?+ HashSet(const uint8_t* ptr, bool make_copy_of_data, size_t* read_count) (art/runtime/base/hash_set.h)

offset = ReadFromBytes(ptr, offset, &temp); // 从ptr, offset为0开始读64位,即8字节

num_elements_ = static_cast(temp); // 读出值给num_elements_

因为大小端的原因,0x2000~0x2007值即为0x0800 0000, 即 134217728,和出错信息里的值num_elements_=134217728是吻合的。

到这里,我们终于搞清楚了,原来 文件本身生成就是有问题,即我们接下来看看

为啥生成会有问题?

文件生成

结合oatdump信息 SectionInternedStrings SectionImageBitmap地址重合,

所以有理由怀疑是读到了ImageBitmap段的数据,那就看看这个写入了些啥数据吧。调查方向:

性能 -> dalvik cache -> patchoat crash -> num_elements_ <= num_buckets_ -> 为啥num_elements_不为0 -> 确定文件和偏移 -> 文件本身问题 -> ImageBitmap段的数据

通过搜索关键字 kSectionImageBitmap 觉得 art/compiler/image_writer.cc 应该是编译时生成文件的代码所在,

通过进一步分析代码,搜索关键字 image_bitmap_,觉得在AllocMemory()

CreateHeader()

CopyAndFixupObject()

Write()

...

等地方都有可能,我们可以在可疑地方把数据dump一下,进一步确认,

添加的日志示例如下:@@ -305,6 +306,17 @@ bool ImageWriter::Write(int image_fd,

......

const ImageSection& bitmap_section = image_header->GetImageSection(

ImageHeader::kSectionImageBitmap); // bitmap_section

// Align up since data size may be unaligned if the image is compressed.

size_t bitmap_position_in_file = RoundUp(sizeof(ImageHeader) + data_size, kPageSize); //

// 添加这个是为了方便确定当前输出文件,确定其偏移

+ PLOG(ERROR) << "testlog write image file " << image_filename << " position:0x" << std::hex << bitmap_position_in_file << std::endl;

// dump 代码省略

// 写文件

if (!image_file->PwriteFully(reinterpret_cast(image_info.image_bitmap_->Begin()),

bitmap_section.Size(),

bitmap_position_in_file)) {

......

@@ -2210,8 +2236,29 @@ void ImageWriter::CopyAndFixupObject(Object* obj) {

auto* dst = reinterpret_cast(image_info.image_->Begin() + offset);

......

+ {

// char *p = reinterpret_cast(image_info.image_bitmap_->Begin());

+ for (size_t tempi=0; tempi < obj->SizeOf() && tempi < 5; ++tempi) {

+ int what = *p;

+ PLOG(ERROR) << "testlog CopyAndFixupObject dump3 " << what << " p:" << (void *) p << std::endl;

+ p++;

+ }

+ }

// Set()前后都添加了dump, 以进一步确认

image_info.image_bitmap_->Set(dst); // Mark the obj as live.

+ {

+ // char *p = reinterpret_cast(image_info.image_bitmap_->Begin());

+ char *p = (char*)(image_info.image_bitmap_->Begin());

+ for (size_t tempi=0; tempi < obj->SizeOf() && tempi < 5; ++tempi) {

+ int what = *p;

+ PLOG(ERROR) << "testlog CopyAndFixupObject dump5 " << what << " p:" << (void *) p << std::endl;

+ p++;

+ }

+ }

通过分析 mm 模块编译的日志// 先通过write时的文件名和dump数据时位置为0x7f16a82a4003

dex2oatd E 01-07 17:30:44 798494 798494 image_writer.cc:309] testlog write image file out/target/product/.../dex_bootjars/system/framework/arm/boot-android.hardware.noname-V1.0-java.art position:0x2000

dex2oatd E 01-07 17:30:44 798494 798494 image_writer.cc:315] testlog write buffer dump2 0 p:0x7f16a82a4000

dex2oatd E 01-07 17:30:44 798494 798494 image_writer.cc:315] testlog write buffer dump2 0 p:0x7f16a82a4001

dex2oatd E 01-07 17:30:44 798494 798494 image_writer.cc:315] testlog write buffer dump2 0 p:0x7f16a82a4002

dex2oatd E 01-07 17:30:44 798494 798494 image_writer.cc:315] testlog write buffer dump2 8 p:0x7f16a82a4003

// 再次过滤日志,发现 dump3和dump5时数据就变为8了, 对应加的日志,即为 image_info.image_bitmap_->Set(dst); 前后

dex2oatd E 01-07 17:30:41 798494 798494 image_writer.cc:2231] testlog CopyAndFixupObject dump3 0 p:0x7f16a82a4003

dex2oatd E 01-07 17:30:41 798494 798494 image_writer.cc:2242] testlog CopyAndFixupObject dump5 8 p:0x7f16a82a4003

最终定位在image_info.image_bitmap_->Set(dst)之后数据就有问题了,

具体代码:art/compiler/image_writer.cc

void ImageWriter::CopyAndFixupObject(Object* obj) {...

image_info.image_bitmap_->Set(dst); // Mark the obj as live

art/runtime/gc/accounting/space_bitmap.h

bool Set(const mirror::Object* obj) ALWAYS_INLINE {

return Modify(obj);

}

art/runtime/gc/accounting/space_bitmap-inl.h

inline bool SpaceBitmap::Modify(const mirror::Object* obj) {

uintptr_t addr = reinterpret_cast(obj);

......

const uintptr_t mask = OffsetToMask(offset);

DCHECK_LT(index, bitmap_size_ / sizeof(intptr_t)) << " bitmap_size_ = " << bitmap_size_;

Atomic* atomic_entry = &bitmap_begin_[index]; //

uintptr_t old_word = atomic_entry->LoadRelaxed();

if (kSetBit) {

// Check the bit before setting the word incase we are trying to mark a read only bitmap

// like an image space bitmap. This bitmap is mapped as read only and will fault if we

// attempt to change any words. Since all of the objects are marked, this will never

// occur if we check before setting the bit. This also prevents dirty pages that would

// occur if the bitmap was read write and we did not check the bit.

if ((old_word & mask) == 0) {

atomic_entry->StoreRelaxed(old_word | mask); //

虽然吧……看了老半天也没太懂StoreRelaxed()究竟咋存的,也不太想继续研究,但感觉应该OK的代码这个值也是这样,

那 为啥OK时读bitmap section时这个值没出错呢?

那就看看OK时的情况吧,编译了OK时的boot-android.hardware.noname-V1.0-java.art,用vim -b查看0x2000的值居然都为0,00002000: 0000 0000 0000 0000 0000 0000 0000 0000 ................

奇了怪了……

偶然发现,原来!他的偏移为0x3000// oatdump OK时的信息

SpaceTypeImageSpace begin=0x70810000,end=0x70810138,size=312B,name="/home/.../out/target/product/.../system/framework/arm/boot-android.hardware.noname-V1.0-java.art"]

SectionObjects 0x70810000-0x70810138

SectionArtFields 0x70810138-0x70810138

......

SectionDexCacheArrays 0x70810138-0x70812008

SectionInternedStrings 0x70812008-0x70812008

SectionClassTable 0x70812008-0x70812008 // 结束地址为 0x2008

SectionImageBitmap 0x70813000-0x70814000 // Bitmap起始地址为0x3000

// mm模块时日志输出也表明偏移为0x3000

dex2oatd E 01-07 17:03:19 790902 790902 image_writer.cc:309] testlog write image file out/target/product/.../dex_bootjars/system/framework/arm/boot-android.hardware.noname-V1.0-java.art position:0x3000

那再看0x3000地址的值,果然和NG时0x2000地址值是一样的,都为0x800 000000002000: 0000 0000 0000 0000 0000 0000 0000 0000 ................

// 0x2008之后的8字节都为0

00002010: 0000 0000 0000 0000 0000 0000 0000 0000 ................

0x3000地址的值0x800 0000

00003000: 0000 0008 0000 0000 0000 0000 0000 0000 ................

这时候就只剩下最后个问题了,

为啥OK时SectionClassTable和SectionImageBitmap的地址没重合呢?

看之前代码分析,ImageWriter::Write()的时要求kPageSize也就是4K(4096)对齐,art/compiler/image_writer.cc

bool ImageWriter::Write(int image_fd,

......

// Align up since data size may be unaligned if the image is compressed.

size_t bitmap_position_in_file = RoundUp(sizeof(ImageHeader) + data_size, kPageSize);

0x2008(即十进制数8200)不能整除4096, 下一个能整除的是0x3000,

至此,整个事情就明了了,剩下的就是修复问题了。

结果

从以上详细分析来看,NG时编译出来的段的偏移为0x2000, 刚好能4K对齐,InternedStrings和ImageBitmap重叠,

所以如果InternedStrings section 大小为零时继续读,就读到了后面bitmap段的数据了。

而OK时段偏移为0x2008, 下一个4K对齐的地址为0x3000, 0x2008后两个8字节数据都刚好为0,所以刚好就没问题了。

如果非要用个图来表示的话,大概是这样的 (图中数据存储用的大端模式,方便画,数据为16进制)NG OK

+----+ + 4K对齐 +----+

0x2000| 00 +--+ InternedStrings 0x2000| 00 |

0x2001| 00 | | ClassTable ...| 00 |

0x2002| 00 | + ImageBitmap ...| 00 |

0x2003| 08 | . | 00 |

...| 00 | . | . |

...|... | . | . | +

0x2007| 00 | 0x2007| | | 非4K对齐

+----+ 0x2008| 00 +--+ InternedStrings

. | 00 | | ClassTable

0x2010| 00 | +

. | . |

0x3000| 00 +--+ ImageBitmap

0x3001| 00 | + 4K对齐

0x3002| 00 |

0x3003| 08 |

0x3004| 00 |

...| . |

0x3007| 00 |

+----+

解决方法:

段的大小为零时直接return,不做处理就行(PatchClassTable()其实也已经这样处理了)。art/patchoat/patchoat.cc

void PatchOat::PatchInternedStrings(const ImageHeader* image_header) {

const auto& section = image_header->GetImageSection(ImageHeader::kSectionInternedStrings);

+ if (section.Size() == 0) {

+ return;

+ }

总结

art对于我来说刚开始感觉很高深,心里就有点怂了,认为搞不定搞不定搞不宝,不过嘛,bug嘛,又不是实现,不用怕。

解决的过程和手段其实和别的问题也差不多:收集信息,看代码,分析日志,根据新线索调整方向,用工具,加log.....整个调查方向进程:性能

-> dalvik cache

-> patchoat crash +-> 为啥num_buckets_不是大于0x8000000的数

-> num_elements_ <= num_buckets_ | -> ELF/art格式

| -> InternedStrings

| -> 编译原理?

| -> 死胡同

| |

| ﹀

+-> 为啥num_elements_不为0

-> 确定文件和偏移

-> 文件本身问题

-> ImageBitmap段的数据

-> 4K对齐,读了后面的数据工具:工具说明oatdumpdump art oat文件信息,功能类似dexdump,objdump

vim -b用vim以16进制方式查看文件

readelfelf文件信息读取ELF 资料另外,知道了改了与boot相关的需要替换所有的boot*adb push $ANDROID_PRODUCT_OUT/system/framework/arm/boot* /system/framework/arm/

adb push $ANDROID_PRODUCT_OUT/system/framework/arm64/boot* /system/framework/arm64/

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值