当Android发生Native Crash时,Coredump能为我们带来什么信息?

​更多内核安全、eBPF分析和实践文章,请关注博客和公众号:

CSDN博客:内核功守道
公众号: 内核功守道

1、概述
1.1 Coredump形成原因
1.2 Coredump的作用

2、使能Coredump功能
2.1 代码修改-MTK平台
2.2 调试方法-MTK平台
2.3 代码修改-高通平台
2.4 调试方法-高通平台

3、Coredump实例分析
3.1 gdb调试基本要求
3.2 解析Coredump
3.3 增加调试LOG
3.4 定位问题点
3.5 解决方案

在这里插入图片描述

1、概述

Core dump又叫核心转储, 当程序运行过程中发生异常, 程序异常退出时, 由操作系统把程序当前的内存状况存储在一个core文件中, 叫core dump. (linux中如果内存越界会收到SIGSEGV信号,然后就会core dump)。

当一个程序奔溃时,在进程当前工作目录的Core 文件中复制了该进程的存储图像。Core文件仅仅是一个内存映像(同时加上调试信息),主要用来调试,通常情况下,Core 文件会包含程序运行时的内存、寄存器状态、堆栈指针、内存管理信息还有各种函数调用堆栈信息等。我们可以理解为是程序工作当前状态存储生成第一个文件,许多程序出错时都会产生一个Core 文件,通过工具分析这个文件,我们可以定位到程序异常退出时对应的堆栈调用等信息,找出问题所在并进行及时解决。

在程序运行的过程中,有的时候我们会遇到Segment fault(段错误)这样的错误。这种看起来比较困难,因为没有任何的栈、trace信息输出。该种类型的错误往往与指针操作相关,往往可以通过Coredump的方式进行定位分析问题。

1.1 Coredump形成原因

1 内存访问越界

  • 由于使用错误的下标,导致数组访问越界。
  • 搜索字符串时,依靠字符串结束符来判断字符串是否结束,但是字符串没有正常的使用结束符。
  • 使用strcpy, strcat, sprintf, strcmp, strcasecmp等字符串操作函数,将目标字符串读/写爆。应该使用strncpy, strlcpy, strncat, strlcat, snprintf, strncmp, strncasecmp等函数防止读写越界。

2 多线程读写的数据未加锁保护

  • 对于会被多个线程同时访问的全局数据,应该注意加锁保护,否则很容易造成core dump

3 非法指针

  • 使用空指针
  • 随意使用指针转换
    一个指向一段内存的指针,除非确定这段内存原先就分配为某种结构或类型,或者这种结构或类型的数组,否则不要将它转换为这种结构或类型 的指针,而应该将这段内存拷贝到一个这种结构或类型中,再访问这个结构或类型。这是因为如果这段内存的开始地址不是按照这种结构或类型对齐的,那么访问它 时就很容易因为bus error而core dump.

4 堆栈溢出

  • 不要使用大的局部变量(因为局部变量都分配在栈上),这样容易造成堆栈溢出,破坏系统的栈和堆结构,导致出现莫名其妙的错误。

1.2 Coredump的作用

目前在Android平台上,Coredump主要应用于解决NE问题,也就是当系统发生了native exception导致手机异常重启的时候,这个时候一般会保留一些简单的backtrace信息,但是对于定位一些内存访问异常、内存被踩的问题来说,信息量不充足,这个时候就需要使用到Coredump来进行分析这类问题。

举例来说,如果一个程序在native层发生了段错误时,这通常是由指针错误引起的。简而言之,产生段错误就是访问了错误的内存段,一般是你没有权限,或者根本就不存在对应的物理内存,尤其常见的是访问0 地址。

一般而言,段错误就是指访问的内存超出了系统所给这个程序的内存空间,通常这个值是由gdtr 来保存的,这是一个48位的寄存器,其中的32位是保存由它指向的gdt 表,后13位保存相应于gdt 的下标,最后3位包括了程序是否在内存中以及程序在CPU 中的运行级别。指向的gdt 是由以64位为一个单位的表,在这张表中就保存着程序运行的代码段以及数据段的起始地址以及与此相应的段限、页面交换、程序运行级别、内存粒度等的信息。一旦一个程序发生了越界访问,CPU 就会产生相应的异常保护,于是Segmentation fault就出现了。

全局描述符表GDT(Global Descriptor Table)在整个系统中,全局描述符表GDT只有一张(一个处理器对应一个GDT),GDT可以被放在内存的任何位置,但CPU必须知道GDT的入口,也就是基地址放在哪里,寄存器GDTR用来存放GDT的入口地址。

MTK平台和高通平台在使能以及使用Coredump进行调试的步骤上有些不同,后续会进行详细分析。

2、使能Coredump功能

2.1 代码修改-MTK平台

在MTK平台上,由于MTK自己封装了一套的抓取log的工具-AEE,而且在Android M/N上, 因受security 限制, aee 如果mode 开到3, 权限太大, 会导致安全问题, 后续user/userdebug build 默认设置成了mode 4 , 并且mobile log 无法 直接切成mode 3. 导致开了mtklogger 后也只能抓到fatal db ,而不能抓到普通exception db;
mode 4 :FATAL BD
FATAL
JE、KE、HWT、HW Reboot
SystemServer/SurfaceFlinger/zygote/mediaserver NE

通过上面的列表可以看出来,当aee的mode为4时,只能抓到System Server/SurfaceFlinger/zygote/mediaserver这四种NE的 Coredump
如果需要在user load 中打开mobilelogd 后,能够切换到aee mode3 抓到普通exception db ,可以关闭强制性约束. /vendor/mediatek/proprietary/external/aee/config_external/init.aee.customer.rc on init
setprop ro.aee.enforcing no
on property:ro.build.type=user
setprop persist.aee.core.dump enable
setprop persist.aee.core.direct enable

2.2 调试方法-MTK平台

经过上面代码的修改就会默认产生Coredump 和直接发送signal 会产生Coredump。在修改完代码后,直接编译版本即可。

编译完版本后复现问题,会生成PROCESS_COREDUMP这样的文件,这个文件就是MTK封装后的Coredump。原生的Coredump是存放在/data/core下面,MTK进行了打包封装打包在NE的db当中,使用MTK工具MediatekLogView把db解析出来即可看到Coredump文件。

2.3 代码修改-高通平台

在高通平台上如果出现异常重启时,一般会抓取tombstone文件,但对于NE问题来说,tombstone的内容不足以分析问题,还是需要抓取Coredump进一步分析。

对于高通平台项目来说,开启Coredump的步骤要比MTK平台复杂一些,具体代码修改如下:
1、修改属性服文件property_service.cpp

@@ -41,6 +41,7 @@
#include <sys/types.h>
#include <sys/mman.h>
+#include <sys/resource.h>
#include <private/android_filesystem_config.h>
#include <selinux/selinux.h> 
@@ -468,6 +469,22 @@ static void load_override_properties() {
}}
+static int check_rlim_action() {
+ struct rlimit rl;
+ std::string pval = property_get("persist.debug.trace");
+ if(pval == "1") {
+ rl.rlim_cur = RLIM_INFINITY;
+ rl.rlim_max = RLIM_INFINITY;
+ if (setrlimit(RLIMIT_CORE, &rl) < 0) {
+ ERROR("could not enable core file generation");
+ }
+ }
+ return 0;
+}



/* When booting an encrypted system, /data is not mounted
@@ -477,6 +494,10 @@ void load_persist_props(void) {
load_override_properties();
/* Read persistent properties after all default values have been loaded. */
load_persistent_properties();
+ /*check for coredump*/
+ check_rlim_action();
}

2、当修改完property_service.cpp后,还需要revert以下两个Patch

(1)https://source.codeaurora.org/external/gigabyte/platform/system/core/patch/libcutils/debugger.c?id=25adbcce267d3f243294f9f0afcc6900904f3419
diff --git a/libcutils/debugger.c b/libcutils/debugger.c
index 3407ec3..4f82b39 100644
--- a/libcutils/debugger.c
+++ b/libcutils/debugger.c
@@ -76,7 +76,9 @@ static int make_dump_request(debugger_action_t action, pid_t tid, int timeout_se
}
int dump_backtrace_to_file(pid_t tid, int fd) {
-  return dump_backtrace_to_file_timeout(tid, fd, 0);
+  // Kind of a hack;
+  // Use a timeout of 5 seconds for a given native proc
+  return dump_backtrace_to_file_timeout(tid, fd, 5);
 	}
int dump_backtrace_to_file_timeout(pid_t tid, int fd, int timeout_secs) {
@@ -89,13 +91,23 @@ int dump_backtrace_to_file_timeout(pid_t tid, int fd, int timeout_secs) {
int result = 0;
char buffer[1024];
ssize_t n;
+  int flag = 0;
+
while ((n = TEMP_FAILURE_RETRY(read(sock_fd, buffer, sizeof(buffer)))) > 0) {
+    flag = 1;
    	 if (TEMP_FAILURE_RETRY(write(fd, buffer, n)) != n) {
       result = -1;
       break;
     }
   }
   close(sock_fd);
+
+  if (flag == 0) {
+    ALOGE("Not even a single byte was read from debuggerd, for pid: %d", tid);
+  }
+  if (result == -1) {
+    ALOGE("Failure(probably timeout) while reading data from debuggerd, for pid: %d", tid);
+  }
   return result;
 }
(2)https://source.codeaurora.org/external/gigabyte/platform/system/core/commit/debuggerd/debuggerd.cpp?h=LA.UM.5.6.r1-01900-89xx.0&id=25adbcce267d3f243294f9f0afcc6900904f3419
diff --git a/debuggerd/debuggerd.cpp b/debuggerd/debuggerd.cpp
index 908af10..1ea6c8e 100644
--- a/debuggerd/debuggerd.cpp
+++ b/debuggerd/debuggerd.cpp
@@ -671,7 +671,8 @@ static void worker_process(int fd, debugger_request_t& request) {
 }
 
 static void monitor_worker_process(int child_pid, const debugger_request_t& request) {
-  struct timespec timeout = {.tv_sec = 10, .tv_nsec = 0 };
+  // set the timeout to 3 sec, for trace collection
+  struct timespec timeout = {.tv_sec = 3, .tv_nsec = 0 };
   if (should_attach_gdb(request)) {
     // If wait_for_gdb is enabled, set the timeout to something large.
     timeout.tv_sec = INT_MAX;

2.4 调试方法-高通平台

在上述代码都修改完后,build一个eng版本。刷机后连接adb,下面进入验证阶段:

1、 adb shell  getprop | grep ro.debuggable   

这个值显示为1即为OK,eng默认为1

2、 adb shell  getprop | grep persist.debug.trace

如果没有显示任何值的话,请输入

	adb shell setprop persist.debug.trace 1

然后再次输入getprop | grep persist.debug.trace,查看是否设置成功

3、 输入下面三行命令:

	adb shell setenforce 0
 	adb shell stop
 	adb shell start
这个时候手机会自动重启,待重启后进行下面的步骤。
4、 adb shell  getenforce   

如果是Permissive,则进行步骤5
如果是Enforcing,则输入adb shell setenforce 0

5、 此时各种属性已经设置完毕,需要获取异常程序的PID,以system_server为例
 	adb shell ps | grep system_server,会得到pid(第一个数值)
6、 adb shell kill -11 “pid”  例如kill -11 1800,之后手机会重启
7、 重启之后,进入adb shell /data/,查看在data目录里应该有个core的文件夹,core!system!bin!app_process64.4915.system_server ,如果存在类似于前面名称的文件即为生成了coredump文件,可以进行进一步分析。

3、Coredump实例分析

3.1 gdb调试基本要求

在使用gdb分析Coredump上,MTK和高通基本上是一致,只有在加载Coredump时是不一样的,因为MTK使用的PROCESS_COREDUMP,而高通使用是Android 原生名称的Coredump文件类似于!system!bin!app_process64.4915.system_server这样的文件名称。

Gdb离线调试Coredump,一定要有这个程序的symbols文件才行。在分析Coredump前,先选对gdb,如果gdb选错会造成解析失败,目前项目一般是64位的Gdb

当MTK平台项目发生NE异常重启后,会在mtklog里面产生db文件。当我们解析完db后,会在解析后的文件夹里面找到Coredump–PROCESS_COREDUMP,假设代码中gdb路径:/home/alps/prebuilts/gcc/linux-x86/arm/arm-linux-androideabi-4.8/bin/arm-linux-androideabi-gdb。
symbols路径:/home/alps/out/target/product/$proj/symbols。开始启动gdb,app对应的程序为symbol路径的system/bin/app_process64:

MTK平台命令:
arm-linux-androideabi-gdb system/bin/app_process64 PROCESS_COREDUMP
高通:
arm-linux-androideabi-gdb system/bin/app_process64 !system!bin!app_process64.4915.system_server

当进入gdb后,还需要设置一下symbol路径,以便解析时找到正确的symbol路径。

(gdb) set solib-search-path <symbol_files_path>
Example: set solib-search-path symbols/system/lib 

到这里就完成了gdb的启动、加载和symbol路径设置,之后就可以自由使用各种命令分析NE问题了,比如bt,info registers等。这个需要使用者对gdb 相关命令有深入充分的了解,通过各种命令分析Coredump,最终达到分析解决问题的目的。

在设置完symbol路径后,MTK和高通平台的分析流程都是一样的,下面用一个高通项目的实例来讲解下使用gdb对Coredump的解析,以及相关NE应该如何处理的步骤。

3.2 解析Coredump

在高通某平台上遇到了一个在art里面system_server进程里面的android.display线程会大概率的收到SIGSEGV,触发NE最终异常重启的问题,问题是发生在Android runtime的GC过程中。

问题现象为当测试同学操作短信和微信分屏时,此时操作短信中的拨打电话功能,就会出现NE,手机发生上层重启。下面讲解一下如何使用gdb解析Coredump,分析和解决这个问题的过程。

  • 查看tombstones里面的backtrace信息,具体log显示如下:
11-24 14:31:25.990 12137 12137 F DEBUG   : pid: 1802, tid: 1828, name: android.display  >>> system_server <<<
11-24 14:31:25.990 12137 12137 F DEBUG   : signal 11 (SIGSEGV), code 2 (SEGV_ACCERR), fault addr 0x3c342e44
11-24 14:31:26.019 12137 12137 F DEBUG   : Abort message: 'art/runtime/gc/space/large_object_space.cc:547] ART_debugging: obj is 0x7711f000  in function FreeListSpace::Alloc'
11-24 14:31:26.019 12137 12137 F DEBUG   :     x0   00000000770dd000  x1   0000000000000000  x2   0000007f6bf71c00  x3   0000000000000000
11-24 14:31:26.020 12137 12137 F DEBUG   :     x4   0000000000000171  x5   0080000000000000  x6   0000007f85d4a000  x7   0000000000000000
11-24 14:31:26.020 12137 12137 F DEBUG   :     x8   000000003c342e34  x9   0000000000000001  x10  0000000000000001  x11  0000000000000000

通过以上tombstones的backtrace信息可知,该问题主要是由于在寄存器X0对象的地址发生了异常,0x770dd000 发生异常变成了0x3c342e44,引起signal 11(SIGSEGV),触发NE,最终导致手机重启。

目前通过tombstones已经无法判断出具体的问题点,需要使用gdb来对复现问题后抓取的coredump进行一步步的解析,来确定问题发生点。

  • 首先按照前面所讲的步骤加载coredump,进入gdb,然后进行以下操作:
arm-linux-androideabi-gdb symbols !system!bin!app_process64.4915.system_server
(gdb) bt
#0  0x0000007f761cb98c in art::mirror::ObjectReference<false, art::mirror::Class>::UnCompress (this=<optimized out>) at art/runtime/mirror/object_reference.h:71
optimized out>, field_offset=...)
………………
#8  art::gc::collector::MarkSweep::MarkObjectSlowPath::operator() (this= 0x770dd000, obj=<optimized out>) at art/runtime/gc/collector/mark_sweep.cc:417 //Mark obj的地址是0x770dd000 

………
通过上面的backtrace,发现当时系统是在处理Mark Sweep这个GC算法时出现了异常,所以我们需要具体分析一下mark_sweep.cc代码,gdb分析如下:

(gdb) f 8
#8  art::gc::collector::MarkSweep::MarkObjectSlowPath::operator() (this=0x7f5d7248b8, obj=<optimized out>) at art/runtime/gc/collector/mark_sweep.cc:417
417             size_t holder_size = holder_->SizeOf();

(gdb) l
412         if (UNLIKELY(obj == nullptr || !IsAligned<kPageSize>(obj) ||
413                      (kIsDebugBuild && large_object_space != nullptr &&
414                          !large_object_space->Contains(obj)))) {
415           LOG(INTERNAL_FATAL) << "Tried to mark " << obj << " not contained by any spaces";
416           if (holder_ != nullptr) {
417             size_t holder_size = holder_->SizeOf();  //触发native crash的地方
418             ArtField* field = holder_->FindFieldByOffset(offset_);
419             LOG(INTERNAL_FATAL) << "Field info: "
420                                 << " holder=" << holder_

417行明显是在做一个sizeof的计算动作,这个时候有可能会有一些size方面的问题会触发crash问题,所以需要着重分析这个地方,由于原生代码的打印log较少,没有将object的调用者及其他信息打印出来,无法直接定位问题点,所以需要增加相关的打印log,主要是打印出这个obj地址的变化过程,找到出现问题的root cause。

3.3 增加调试信息

1、打开kDebugSpaces开关,主要是可以打印出ART运行时为新创建对象分配内存过程的log,类似于下面:

Inline mirror::Object*DlMallocSpace::AllocWithoutGrowthLocked(size_tnum_bytes,size_t*bytes_allocated){
mirror::Object*result=reinterpret_cast(mspace_malloc(mspace_,num_bytes));
if(result!=NULL){
if(kDebugSpaces){
CHECK(Contains(result))<<"Allocation("<(result)
<<")notinboundsofallocationspace"<<*this;
}
代码主要修改如下所示:
diff --git a/runtime/gc/space/space.h b/runtime/gc/space/space.h
index fc558cf..258bf9c 100644
--- a/runtime/gc/space/space.h
+++ b/runtime/gc/space/space.h
@@ -53,7 +53,7 @@ class LargeObjectSpace;
 class RegionSpace;
 class ZygoteSpace;
 
-static constexpr bool kDebugSpaces = kIsDebugBuild;
+static constexpr bool kDebugSpaces = true;
 
 // See Space::GetGcRetentionPolicy.
 enum GcRetentionPolicy {

2、修改art/runtime/base/logging.cc,主要是判断当前进程是否是system_server,来做进一步的排除筛选。

diff --git a/runtime/base/logging.cc b/runtime/base/logging.cc
index df8a369..1c32c89 100644
--- a/runtime/base/logging.cc
+++ b/runtime/base/logging.cc
@@ -61,6 +61,29 @@ const char* GetCmdLine() {
   return (gCmdLine.get() != nullptr) ? gCmdLine->c_str() : nullptr;
 }
 
+bool checkProcessName() {
+    bool result = false;
+    int bufsize = 32;
+
+    FILE* fp = fopen("/proc/self/cmdline", "r");
+    if (fp == nullptr) {
+        return result;
+    }
+
+    char line[bufsize];
+    if (fgets(line, sizeof(line), fp) != nullptr) {
+        if (strncmp(line, "system_server", 13)) {
+            result = false;
+        } else {
+            result = true;
+        }
+    }
+
+    fclose(fp);
+    return result;
+}
+

3、在obj申请和释放space打印出obj的地址

diff --git a/runtime/gc/space/large_object_space.cc b/runtime/gc/space/large_object_space.cc
index 010f677..5b43c95 100644
--- a/runtime/gc/space/large_object_space.cc
+++ b/runtime/gc/space/large_object_space.cc
@@ -35,6 +35,8 @@ namespace art {
 namespace gc {
 namespace space {
 
+static constexpr bool kIsDebugBuild = true;
+
 class MemoryToolLargeObjectMapSpace FINAL : public LargeObjectMapSpace {
  public:
   explicit MemoryToolLargeObjectMapSpace(const std::string& name) : LargeObjectMapSpace(name) {
@@ -64,6 +66,9 @@ class MemoryToolLargeObjectMapSpace FINAL : public LargeObjectMapSpace {
     if (usable_size != nullptr) {
       *usable_size = num_bytes;  // Since we have redzones, shrink the usable size.
     }
+    if (checkProcessName() && obj != nullptr) {
+      LOG(INTERNAL_FATAL) << "ART_debugging: obj is " << obj << " in function MemoryToolLargeObjectMapSpace::Alloc";
+    }
     return object_without_rdz;
   }
 
@@ -78,6 +83,10 @@ class MemoryToolLargeObjectMapSpace FINAL : public LargeObjectMapSpace {
   size_t Free(Thread* self, mirror::Object* obj) OVERRIDE {
     mirror::Object* object_with_rdz = ObjectWithRedzone(obj);
     MEMORY_TOOL_MAKE_UNDEFINED(object_with_rdz, AllocationSize(obj, nullptr));
+
+    if (checkProcessName() && obj != nullptr) {
+      LOG(INTERNAL_FATAL) << "ART_debugging: obj is " << obj << " in function MemoryToolLargeObjectMapSpace::Free";
+    }
     return LargeObjectMapSpace::Free(self, object_with_rdz);
   }
 
@@ -170,6 +179,9 @@ mirror::Object* LargeObjectMapSpace::Alloc(Thread* self, size_t num_bytes,
   total_bytes_allocated_ += allocation_size;
   ++num_objects_allocated_;
   ++total_objects_allocated_;
+  if (checkProcessName() && obj != nullptr) {
+      LOG(INTERNAL_FATAL) << "ART_debugging: obj is " << obj << " in function LargeObjectMapSpace::Alloc";
+  }
   return obj;
 }
 
@@ -177,6 +189,9 @@ bool LargeObjectMapSpace::IsZygoteLargeObject(Thread* self, mirror::Object* obj)
   MutexLock mu(self, lock_);
   auto it = large_objects_.find(obj);
   CHECK(it != large_objects_.end());
+  if (checkProcessName() && obj != nullptr) {
+      LOG(INTERNAL_FATAL) << "ART_debugging: obj is " << obj << " in function LargeObjectMapSpace::IsZygoteLargeObject";
+  }
   return it->second.is_zygote;
 }
 
@@ -195,6 +210,9 @@ size_t LargeObjectMapSpace::Free(Thread* self, mirror::Object* ptr) {
     Runtime::Current()->GetHeap()->DumpSpaces(LOG(INTERNAL_FATAL));
     LOG(FATAL) << "Attempted to free large object " << ptr << " which was not live";
   }
+  if (checkProcessName() && ptr != nullptr) {
+      LOG(INTERNAL_FATAL) << "ART_debugging: obj is " << ptr << " in function LargeObjectMapSpace::Free";
+  }
   MemMap* mem_map = it->second.mem_map;
   const size_t map_size = mem_map->BaseSize();
   DCHECK_GE(num_bytes_allocated_, map_size);
@@ -458,6 +476,12 @@ size_t FreeListSpace::Free(Thread* self, mirror::Object* obj) {
   DCHECK_LE(allocation_size, num_bytes_allocated_);
   num_bytes_allocated_ -= allocation_size;
   madvise(obj, allocation_size, MADV_DONTNEED);
+
+
+  if (checkProcessName() && obj != nullptr ) {
+      LOG(INTERNAL_FATAL) << "ART_debugging: obj is " << obj << " in function FreeListSpace::Free";
+  }
+
   if (kIsDebugBuild) {
     // Can't disallow reads since we use them to find next chunks during coalescing.
     mprotect(obj, allocation_size, PROT_READ);
@@ -523,6 +547,11 @@ mirror::Object* FreeListSpace::Alloc(Thread* self, size_t num_bytes, size_t* byt
   num_bytes_allocated_ += allocation_size;
   total_bytes_allocated_ += allocation_size;
   mirror::Object* obj = reinterpret_cast<mirror::Object*>(GetAddressForAllocationInfo(new_info));
+
+  if (checkProcessName() && obj != nullptr ) {
+      LOG(INTERNAL_FATAL) << "ART_debugging: obj is " << obj << " in function FreeListSpace::Alloc";
+  }
+
   // We always put our object at the start of the free block, there cannot be another free block
   // before it.
   if (kIsDebugBuild) {

4、最关键也是最重要的一步,在mark sweep算法代码里增加打印出hold对象的信息,包括size、type、offset等等,这样就可以一目了然的看出来这个obj是被谁申请的,type是什么,从而最终确定问题点。

diff --git a/runtime/gc/collector/mark_sweep.cc b/runtime/gc/collector/mark_sweep.cc
index 24cbf10..4491c9d 100644
--- a/runtime/gc/collector/mark_sweep.cc
+++ b/runtime/gc/collector/mark_sweep.cc
@@ -22,6 +22,10 @@
 #include <climits>
 #include <vector>
 
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
 #include "base/bounded_fifo.h"
 #include "base/logging.h"
 #include "base/macros.h"
@@ -409,6 +413,48 @@ class MarkSweep::MarkObjectSlowPath {
       ++mark_sweep_->large_object_mark_;
     }
     space::LargeObjectSpace* large_object_space = mark_sweep_->GetHeap()->GetLargeObjectsSpace();
+
+    if (checkProcessName()) {
+        if (!IsAligned<kPageSize>(obj)) {
+            LOG(INTERNAL_FATAL) << "ART_debugging: Tried to mark " << obj << " not aligned";
+        } else if (!large_object_space->Contains(obj)){
+            LOG(INTERNAL_FATAL) << "ART_debugging: Tried to mark " << obj << " not contained by memory spaces";
+	} else {
+            LOG(INTERNAL_FATAL) << "ART_debugging: Tried to mark " << obj << " contained by memory spaces";
+	      if (holder_ != nullptr) {
+		size_t holder_size = holder_->SizeOf();
+		ArtField* field = holder_->FindFieldByOffset(offset_);
+		LOG(INTERNAL_FATAL) << "Field info: "
+		                    << " holder=" << holder_
+		                    << " holder is "
+		                    << (mark_sweep_->GetHeap()->IsLiveObjectLocked(holder_)
+		                        ? "alive" : "dead")
+		                    << " holder_size=" << holder_size
+		                    << " holder_type=" << PrettyTypeOf(holder_)
+		                    << " offset=" << offset_.Uint32Value()
+		                    << " field=" << (field != nullptr ? field->GetName() : "nullptr")
+		                    << " field_type="
+		                    << (field != nullptr ? field->GetTypeDescriptor() : "")
+		                    << " first_ref_field_offset="
+		                    << (holder_->IsClass()
+		                        ? holder_->AsClass()->GetFirstReferenceStaticFieldOffset(
+		                            sizeof(void*))
+		                        : holder_->GetClass()->GetFirstReferenceInstanceFieldOffset())
+		                    << " num_of_ref_fields="
+		                    << (holder_->IsClass()
+		                        ? holder_->AsClass()->NumReferenceStaticFields()
+		                        : holder_->GetClass()->NumReferenceInstanceFields())
+		                    << "\n";
+		// Print the memory content of the holder.
+		for (size_t i = 0; i < holder_size / sizeof(uint32_t); ++i) {
+		  uint32_t* p = reinterpret_cast<uint32_t*>(holder_);
+		  LOG(INTERNAL_FATAL) << &p[i] << ": " << "holder+" << (i * sizeof(uint32_t)) << " = "
+		                      << std::hex << p[i];
+		}
+            }
+	}
+    }
+
	if (UNLIKELY(obj == nullptr || !IsAligned<kPageSize>(obj) ||
                  (kIsDebugBuild && large_object_space != nullptr &&
                      !large_object_space->Contains(obj)))) {

3.4 定位问题点
下面是添加了debug code复现问题后抓取的coredump和logcat,根据以下log最终分析定位出问题点,并解决问题。

11-24 14:24:33.193  1802  1817 F art     : art/runtime/gc/space/large_object_space.cc:547] ART_debugging: obj is 0x770dd000 in function FreeListSpace::Alloc
11-24 14:24:37.041  1802  1812 F art     : art/runtime/gc/space/large_object_space.cc:479] ART_debugging: obj is 0x770dd000 in function FreeListSpace::Free
11-24 14:25:12.407  1802  7098 F art     : art/runtime/gc/space/large_object_space.cc:547] ART_debugging: obj is 0x770dd000 in function FreeListSpace::Alloc
11-24 14:25:16.409  1802  1812 F art     : art/runtime/gc/space/large_object_space.cc:479] ART_debugging: obj is 0x770dd000 in function FreeListSpace::Free

通过上面的log我们可以看到,在上面的时间点时,obj的申请使用和释放的地址都是0x770dd000,此时地址使用正常。

11-24 14:31:20.991  1802  8922 D GioneeBlur: execute method!
11-24 14:31:20.992  1802  8922 D GioneeBlur: Thread name = GioneeBlur #1
11-24 14:31:20.992  1802  8922 D GioneeBlur: bmpWidth = 27,bmpHeight = 403   //<<< GioneeBlur 申请了使用 地址“0x770dd000”
11-24 14:31:20.992  1802  8922 D GioneeBlur: nativeProcessBitmap start
11-24 14:31:20.992  1802  8922 D GioneeBlur: blurRatio = 5
11-24 14:31:20.993  1802  8922 F art     : art/runtime/gc/space/large_object_space.cc:547] ART_debugging: obj is 0x770dd000 in function FreeListSpace::Alloc
11-24 14:31:20.997  1802  8922 D GioneeBlur: elapse time = 3
11-24 14:31:20.997  1802  8922 D GioneeBlur: mListener = false

11-24 14:31:25.744  1802  1828 F art     : art/runtime/gc/collector/mark_sweep.cc:423] ART_debugging: Tried to mark 0x770dd000 contained by memory spaces
11-24 14:31:25.744  1802  1828 F art     : art/runtime/gc/collector/mark_sweep.cc:427] Field info:  holder=0x1431a348 holder is alive holder_size=43 holder_type=android.graphics.Bitmap offset=8 field=mBuffer field_type=[B first_ref_field_offset=8 num_of_ref_fields=3
11-24 14:31:25.745  1802  1828 F art     : art/runtime/gc/collector/mark_sweep.cc:427]

以上log可以明确看到是GioneeBlur申请使用了0x770dd000,主要进行了bitmap的操作。
可以看到在第一次Mark 0x770dd000是正确的地址,holder_type=android.graphics.Bitmap。

11-24 14:31:25.745  1802  1828 F art     : art/runtime/gc/collector/mark_sweep.cc:419] ART_debugging: Tried to mark 0x3c342e34 not aligned
11-24 14:31:25.745  1802  1828 F art     : art/runtime/gc/collector/mark_sweep.cc:461] Tried to mark 0x3c342e34 not contained by any spaces

而在第二次Mark 这个object时,则出现了错误的地址0x3c342e34。

由以上信息可以看出来,主要是因为X0(0x770dd000)的obj出现了异常,这个obj是由GioneeBlur申请使用,holder type是android.graphics.Bitmap,由此可以分析出,当出现问题的时候是在做bitmap相关计算的操作,而这个GioneeBlur是公司自己实现的一个模糊算法,,正好使用到了bitmap,所以问题出现在了这个地方。

3.5 解决方案
最终经过确认,这个问题主要是因为在使用模块算法库的时候,调用在ActivityStack里面的时候,处理异常传参时数值超出了范围,最终造成异常重启。
最后讲一下这个问题的解决方案,如下:

出问题时的传参:

11-24 15:56:11.232 D/GioneeBlur( 1725): bmpWidth = 27,bmpHeight = 403   (bitmap大小)
11-24 15:56:11.232 D/GioneeBlur( 1725): blurRatio = 5   (模糊率)

滤波器窗口长度 = 2^(blurRatio+1),窗口越长模糊的越厉害。
blurRatio=5 时,应窗口长度是64. 超过了宽度(27),所以崩了。

传进的size大小的异常,可能和分辨率、是否有虚拟按键有关。
修改方法是做参数检查。合法条件:

  • blurRatio取值范围:1~5
  • 窗口长度 = 2^(blurRatio-1)必须小于图片长和宽
    如果blurRatio太大,则减小;
  • 1
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

芯光未来

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值