2021-10-14

Android调试之tombstone信息的分析

loongembedded 2017-02-23 20:52:05

1343  正在上传…重新上传取消​ 收藏 

分类专栏: Android调试工具

​ Android调试工具  专栏收录该内容

36 篇文章 3 订阅

订阅专栏

tombstone一般是由Dalvik错误、状态监视调试器、C层代码以及libc的一些问题导致的。当系统发生tombstone的时候,kernel首先会上报一个严重的警告信号(signal),上层接收到之后,进程的调试工具会把进程中当时的调用栈现场保存起来,并在系统创建了data/tombstones目录后把异常时的进程信息写在此目录里面,开发者需要通过调用栈来分析整个调用流程来找出出问题的点。

通常出现错误后可以查看tombstone_0*来查看对应的堆栈信息,例如:
cat /data/tombstones/tombstone_0* 或者 head -N /data/tombstones/tombstone_0*
如果需要查看某一个进程的堆栈也可以,例如:
debuggerd PID 就会将对应进程号的进程堆栈信息存放至/data/tombstones/tombstone_0*文件中。

有一句话叫做常在河边走,哪有不湿鞋。我们这些研究和开发Android的project师正应了这句话,相必大家在调试的时候常常会遇到这么个东西吧

*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
Build fingerprint: 'XXXXXXXXX'
pid: 1658, tid: 13086  >>> system_server <<<
signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 64696f7e
 r0 00000000  r1 00000001  r2 ad12d1e8  r3 7373654d
 r4 64696f72  r5 00000406  r6 00974130  r7 40d14008
 r8 4b857b88  r9 4685adb4  10 00974130  fp 4b857ed8
 ip 00000000  sp 4b857b50  lr afd11108  pc ad115ebc  cpsr 20000030
 d0  4040000040000000  d1  0000004200000003
 d2  4e72cd924285e370  d3  00e81fe04b1b64d8
 d4  3fbc71c7009b64d8  d5  3fe999999999999a
 d6  4010000000000000  d7  4000000000000000
 d8  4000000000000000  d9  0000000000000000
 d10 0000000000000000  d11 0000000000000000
 d12 0000000000000000  d13 0000000000000000
 d14 0000000000000000  d15 0000000000000000
 scr 80000012

         #00  pc 000108d8  /system/lib/libc.so
         #01  pc 0003724c  /system/lib/libxvi020.so
         #02  pc 0000ce02  /system/lib/libxvi020.so
         #03  pc 0000d672  /system/lib/libxvi020.so
         #04  pc 00010cce  /system/lib/libxvi020.so
         #05  pc 00004432  /system/lib/libwimax_jni.so
         #06  pc 00011e74  /system/lib/libdvm.so
         #07  pc 0004354a  /system/lib/libdvm.so
         #08  pc 00017088  /system/lib/libdvm.so
         #09  pc 0001c210  /system/lib/libdvm.so
         #10  pc 0001b0f8  /system/lib/libdvm.so
         #11  pc 00059c24  /system/lib/libdvm.so
         #12  pc 00059e3c  /system/lib/libdvm.so
         #13  pc 0004e19e  /system/lib/libdvm.so
         #14  pc 00011b94  /system/lib/libc.so
         #15  pc 0001173c  /system/lib/libc.so

code around pc:
ad115e9c 4620eddc bf00bd70 0001736e 0001734e
ad115eac 4605b570 447c4c0a f7f44620 e006edc8
ad115ebc 42ab68e3 68a0d103 f7f42122 6864edd2
ad115ecc d1f52c00 44784803 edbef7f4 bf00bd70
ad115edc 00017332 00017312 2100b51f 46682210

code around lr:
afd110e8 e2166903 1a000018 e5945000 e1a02004
afd110f8 e2055a02 e1a00005 e3851001 ebffed92
afd11108 e3500000 13856002 1a000001 ea000009
afd11118 ebfffe50 e1a01004 e1a00006 ebffed92
afd11128 e1a01005 e1550000 e1a02006 e3a03000


stack:
    4b857b10  40e43be8  
    4b857b14  00857280  
    4b857b18  00000000  
    4b857b1c  034e8968  
    4b857b20  ad118ce9  /system/lib/libnativehelper.so
    4b857b24  00000002  
    4b857b28  00000406

    .....    

    初一看到直接崩溃,我的个妈,这么一大坨,啥玩意啊,全然搞不懂,没有头绪撒。这NND Android出了问题,冒出这么大堆的地址,搞毛呢。

   事实上, 我能够非常负责任的告诉你,兄弟这么多事实上我也看不懂,在我眼里除了红色和蓝色的地方,其它全是废话。由于除了这两个地方意外,其它的我确实看不懂,一堆线性地址,外加寄存器,跟火星文一样。

   算了,废话不多少,直接分析:

   1 红色的地方,是让我们确认问题究竟发生在那个线程中,是主线程还是子线程,这个的推断根据是:假设PID和TID同样,恭喜你问题出在父亲这边,看样子问题还比較好办。假设PID和TID不同样,那么您悲剧了,问题出在子线程中。根据我的经验,100的tombstone中,问题出如今父线程的概率,我还从来没发现,尤其是什么system_server, zygote这些,基本都是儿子的问题。唉,谁叫父亲英雄,儿狗熊呢。。。

  2 确认了问题的基本点,以下就是開始看蓝色的部分了。

    这个蓝色不分是从#00-->#XX 表面上看是从上往下的增长,事实上坑爹啊,它所显示的程序的运行流程,恰恰是从下往上的也就是实际的运行顺序是#XX-->#00。

   所以一開始我们的目的是分析第一个出现故障的动态连接库
   #15  pc 0001173c  /system/lib/libc.so

  a 首先按图索骥,找到你的目标,libc.so这个通常会在你编译完的目标文件夹下,也就是out/target/product/your_pro/system/lib这个文件夹下。

  b 一般你假设用的是还像样的linux系统,会有个地址解析的命令addr2line,负责解析动态连接库的(假设你是悲剧,没有这个命令,那好吧, google早就预料到会有象你这种悲剧人,他们在prebuilt/linux-x86/toolchain/arm-eabi-4.4.0/bin/arm-eabi-addr2line,给您准备了个,咋就用这个吧,功能一样,没啥差别)

     addr2line -e -f libc.so 0001173c  红色是你的目标库,绿色是你出问题的地址,看看#15这行

    结果出现:??pthread_create

                     ??:0

    恩,好了问题应该出在这个函数中,觉得大功告成了?我,呸,还早呢。这个仅仅是告诉你函数入口,至于详细运行到这个函数的哪个地方挂了,还得再看。

c 找到了是哪个部分出了问题,以下就是开启显微镜,看看谁搞的东东。

   使用objdump -S -D libc.so > deassmble_libc.txt 反汇编下你的动态连接库文件,而且将它写入一个文件里。

   打开这个反汇编过后的重定向文件,在查询的时候输入1173c这个偏移地址,你会看到在茫茫人海中

  00011684 <pthread_create>:
   11684:       e92d4ff0        push    {r4, r5, r6, r7, r8, r9, sl, fp, lr}
   11688:       e24dd01c        sub     sp, sp, #28     ; 0x1c
   1168c:       e1a06001        mov     r6, r1
   11690:       e1a08002        mov     r8, r2
   11694:       e1a09003        mov     r9, r3
   11698:       e3a04001        mov     r4, #1  ; 0x1
   1169c:       e59f521c        ldr     r5, [pc, #540]  ; 118c0 <pthread_create+0x23c>
   116a0:       e58d000c        str     r0, [sp, #12]
   116a4:       eb009a35        bl      37f80 <strncmp+0x20>
   116a8:       e59f2214        ldr     r2, [pc, #532]  ; 118c4 <pthread_create+0x240>
   116ac:       e1a03000        mov     r3, r0
   116b0:       e1a01004        mov     r1, r4
   116b4:       e593c000        ldr     ip, [r3]
   116b8:       e3a0003c        mov     r0, #60 ; 0x3c
   116bc:       e08f3005        add     r3, pc, r5
   116c0:       e7933002        ldr     r3, [r3, r2]
   116c4:       e5834000        str     r4, [r3]
   116c8:       e58dc010        str     ip, [sp, #16]
   116cc:       eb009a3b        bl      37fc0 <strncmp+0x60>
   ...

   1173c:       ebffec2b        bl      c7f0 <__pthread_clone>-->就是他了,对你成功了。

   ...

这个是ARM汇编,须要你翻译成相应的C函数去看,这里我就不做解释了,照着前面的步骤,

对上面中#15-->#00 一共16行慢慢去找,直到找到#00行的问题函数,事实上,最后一个#00行的是最重要的(前面不找也行,可是能够多给你提供问题信息,和流程),由于他是第一目击者,也是Crash前的第一现场。所以找到这个函数非常重要,如果我们最后经过万里长针发现#00的出错的地方是pXX->member挂了(MD,我常常遇到这样的问题)。

那么你能够怀疑两个地方:

1 您的指针是空指针,可是现实与理想总是十万八千里,多数情况下非常少出现,并且你分析代码后,也会对自己说怎么可能。绝大多数情况下,从我的经验来讲,非常少会有空指针这样的低级错误,可是不排除哪个2货出现了这么个问题。假设是这个问题,那么恭喜你,你非常幸运。

2 还有就是怀疑越界和内存地址被挤占。就拿我曾经的经验,指针不是空,可是依据汇编码看,是訪问成员变量挂了,这个地址肯定是被占用了。

   针对第2种比較恶心的情况,就须要你看整个log的流程了,须要你看下基本的mainlog关于出现crash前的动作,看看是哪个孙子占用的,以近期原则为先,从下往上看,唉,说句实在话,李白的一句话能够形容整个过程:"蜀道难,难于上青天啊"。工作量大,并且要仔细。我也没什么办法。。。

  另一种情况,就是内存不够,导致了您的地址被挤占了,出现low memory, no more ...这种语句,以及大量出现GC_FOR_MALLOC关于GC的东西(假设是少量的,能够忽略,大量的话),呵呵表明你的某个进程在吞噬你的内存,存在内存泄漏。坑爹啊,这个问题,是最难查的,须要你花大量时间,去看内存的变化。一般看内存的情况是

cat /proc/meminfo

空暇内存=buffer+cache+free这三个字段,Active字段是已经使用的内存,Total不用说,是总的物理内存。(记住 free不高,并不代表你的内存空暇不高,海水不可斗量,须要看全了3个字段的总和才是空暇内存)

假设你想详细跟踪每一个进程的内存使用情况,还是在/proc以下,相应了N多的数字文件,那个事实上是PID号,进去后cat status能够看到

VMRSS XXXKB就是你当前进程的使用内存量,此外另一些其它的内存数据,包含页啊等等。。。里面还有非常多实用的数据,假设你想跟踪全部的进程的内存情况,推介大家能够看看linux ps命令的源代码,看看人家是怎么在/proc下遍历进程,而且提取属性值的。

写个daemon,跟踪一段时间,记录下各进程内存的变化,然后就是通宵的分析。。。。

总之,这类问题,非常难定位,也非常难解决,须要花时间,精力去分析。可是假设你解了,那么相信全部人对你会刮目相看的。包含你的老板,量变引起质变,请记住。

我还在纠结中啊...分析ing

最后再说几句

stack:

#12 476e5eb8  476e5ed8  
    476e5ebc  476e5ed8  
    476e5ec0  00100000  
    476e5ec4  476e5ed8  
    476e5ec8  acaa4d38  
    476e5ecc  005ec9b0  
    476e5ed0  aca4e1a3  /system/lib/libdvm.so
    476e5ed4  476e5ed8  
#13 476e5ed8  005ec9b0  
    476e5edc  005ecae8  
    476e5ee0  476e5f00  
    476e5ee4  aca4e109  /system/lib/libdvm.so
    476e5ee8  005ec9b0  
    476e5eec  afd11b98  /system/lib/libc.so
#14 476e5ef0  476e5f00  
    476e5ef4  005ecae8  
    476e5ef8  45b29b84  
    476e5efc  afd11740  /system/lib/libc.so
#15 476e5f00  476e5f00  
    476e5f04  005ecae8  
    476e5f08  00000009  
    476e5f0c  00000000  
    476e5f10  00000000  
    476e5f14  005ec9b0  
    476e5f18  00000000  
    476e5f1c  00000000  

红色的部分是基地址+偏移地址

比方afd +11740 这个afd你能够从prelink-linux-arm.map找到每一个.so的基地址(比方 libc.so                 0xAFD00000 # [~2M])11740能够从反汇编的文件里找到相应点,可是我不知道这些有什么用。
 

希望这偏小文章能帮助到大家,大家共同进步,有好的方法也欢迎您一起分享。谢谢!

以下是一个老外的摘录早期存于google论坛的内存分析文章(如今没了),我是在他基础上,又加了一些分析和经验之谈,希望上帝保佑让我早点定位到问题

<think> 我们需要将日期时间列转换为仅日期部分(去掉时间部分) 假设列名为'成交日期',数据格式为字符串或datetime类型 我们将提供多种方法,并处理不同情况 </think> # 将 DataFrame 中的日期时间列格式化为日期字符串 将日期时间列从 `2021-10-14 00:00:00` 格式转换为 `2021-10-14` 格式是常见的数据处理任务。以下是几种高效且健壮的解决方案: ## 方法 1: 使用 Pandas 的 dt 访问器 ```python import pandas as pd import numpy as np # 示例 DataFrame data = { '成交日期': [ '2021-10-14 00:00:00', '2022-05-21 14:30:00', '2023-01-05 09:15:00', '2020-12-31 23:59:59', None, # 空值 '2023-07-04 00:00:00', 'invalid_date', # 无效日期 20211014, # 数值类型 '2021-10-14' ] } df = pd.DataFrame(data) # 方法 1.1: 转换为日期格式后提取日期部分 def format_date_column(df, col_name, errors='coerce'): """ 格式化日期列为 YYYY-MM-DD 格式 参数: df: 包含日期列的DataFrame col_name: 日期列名 errors: 错误处理方式 ('coerce' 将无效解析设为NaT) 返回: 格式化后的Series """ # 转换为 datetime 类型,处理无效日期 date_series = pd.to_datetime(df[col_name], errors=errors) # 格式化为字符串 formatted = date_series.dt.strftime('%Y-%m-%d') # 处理原始为日期格式但无时间部分的字符串 mask = (df[col_name].str.len() == 10) if df[col_name].dtype == 'object' else False if mask.any(): formatted[mask] = df.loc[mask, col_name] return formatted # 应用格式化 df['格式化日期'] = format_date_column(df, '成交日期') # 显示结果 print("格式化结果:") print(df) ``` ## 方法 2: 使用字符串操作(高效处理纯字符串) ```python # 方法 2: 字符串切片(仅当所有日期格式一致时适用) def format_date_with_slice(series): """ 使用字符串切片快速格式化日期(适用于格式统一的列) """ # 先转换为字符串 str_series = series.astype(str) # 使用字符串切片 formatted = str_series.str[:10] # 处理长度不足的字符串 short_mask = str_series.str.len() < 10 formatted[short_mask] = series[short_mask].apply( lambda x: x if pd.isna(x) else str(x)[:10] # 处理数值类型 ) return formatted # 应用 df['切片格式化'] = format_date_with_slice(df['成交日期']) print("\n字符串切片方法结果:") print(df[['成交日期', '切片格式化']]) ``` ## 方法 3: 高性能矢量化方法(大型数据集) ```python # 方法 3: 使用 NumPy 矢量化操作 def vectorized_date_format(series): """ 高性能日期格式化(适用于大型数据集) """ # 转换为 datetime 数组 date_array = pd.to_datetime(series, errors='coerce').values # 创建结果数组 result = np.empty(series.shape, dtype='U10') # Unicode 10字符 # 提取日期部分 for i in range(len(date_array)): if pd.isnull(date_array[i]): result[i] = '' else: dt = date_array[i].astype('datetime64[D]') # 提取日期部分 result[i] = str(dt)[:10] return pd.Series(result, index=series.index) # 创建大型数据集测试 large_data = { '成交日期': ['2021-10-14 00:00:00'] * 1000000 } large_df = pd.DataFrame(large_data) # 应用高性能格式化 large_df['格式化日期'] = vectorized_date_format(large_df['成交日期']) print("\n大型数据集处理结果(前5行):") print(large_df.head()) ``` ## 方法 4: 处理混合格式的日期列 ```python # 方法 4: 处理混合格式的日期列 def format_mixed_dates(series): """ 处理混合格式的日期列(包含多种格式) """ def format_single_date(value): if pd.isnull(value): return '' # 尝试不同日期格式 try: # 尝试解析完整日期时间 return pd.to_datetime(value).strftime('%Y-%m-%d') except: try: # 尝试解析纯日期格式 if len(str(value)) == 10 and '-' in str(value): return str(value) # 尝试解析数值型日期(如20211014) elif str(value).isdigit() and len(str(value)) == 8: return f"{str(value)[:4]}-{str(value)[4:6]}-{str(value)[6:8]}" except: return '无效日期' return series.apply(format_single_date) # 应用 df['混合格式处理'] = format_mixed_dates(df['成交日期']) print("\n混合格式处理结果:") print(df[['成交日期', '混合格式处理']]) ``` ## 完整解决方案类 ```python class DateFormatter: def __init__(self, input_format=None, output_format='%Y-%m-%d'): """ 初始化日期格式化器 参数: input_format: 输入日期格式(如'%Y-%m-%d %H:%M:%S') output_format: 输出日期格式(默认'%Y-%m-%d') """ self.input_format = input_format self.output_format = output_format def format(self, series): """ 格式化日期Series 返回: 格式化后的Series """ # 转换为datetime if self.input_format: dates = pd.to_datetime(series, format=self.input_format, errors='coerce') else: dates = pd.to_datetime(series, errors='coerce') # 格式化输出 formatted = dates.dt.strftime(self.output_format) # 处理转换失败的值 failed_mask = formatted.isna() formatted[failed_mask] = series[failed_mask].apply(self._handle_failed_conversion) return formatted def _handle_failed_conversion(self, value): """处理转换失败的日期值""" if pd.isnull(value): return '' # 尝试处理数值型日期 if isinstance(value, (int, float)): try: str_value = str(int(value)) if len(str_value) == 8: # 20211014格式 return f"{str_value[:4]}-{str_value[4:6]}-{str_value[6:8]}" except: pass # 尝试处理字符串 if isinstance(value, str) and len(value) >= 10: return value[:10] return '无效日期' # 使用示例 formatter = DateFormatter() df['类格式化'] = formatter.format(df['成交日期']) print("\n使用类处理的结果:") print(df[['成交日期', '类格式化']]) ``` ## 性能比较与优化建议 1. **小型数据集**: - 推荐使用 `pd.to_datetime().dt.strftime()` 方法 - 简洁易读,处理能力强 2. **大型数据集**: - 推荐使用矢量化方法(方法3) - 避免使用 `apply()`,效率较低 3. **数据验证**: - 添加日期有效性检查: ```python def is_valid_date(date_str): try: pd.to_datetime(date_str) return True except: return False valid_mask = df['成交日期'].apply(is_valid_date) print(f"有效日期比例: {valid_mask.mean():.2%}") ``` 4. **时区处理**: ```python # 如果需要处理时区 df['带时区日期'] = pd.to_datetime(df['成交日期'], utc=True).dt.tz_convert('Asia/Shanghai') ``` ## 常见问题解决方案 ### 问题1: 日期格式不一致 ```python # 处理多种日期格式 def parse_multiple_formats(series): formats = [ '%Y-%m-%d %H:%M:%S', '%Y/%m/%d %H:%M:%S', '%Y%m%d', '%Y-%m-%d' ] for fmt in formats: try: return pd.to_datetime(series, format=fmt, errors='coerce') except: continue return pd.to_datetime(series, errors='coerce') df['统一日期'] = parse_multiple_formats(df['成交日期']).dt.strftime('%Y-%m-%d') ``` ### 问题2: 处理空值和无效日期 ```python # 创建包含空值和无效日期的列 invalid_data = { '日期': [ '2021-10-14 00:00:00', None, '2023-13-01 00:00:00', # 无效月份 '2022-02-30 00:00:00', # 无效日期 'not a date' ] } invalid_df = pd.DataFrame(invalid_data) # 安全转换方法 invalid_df['安全转换'] = pd.to_datetime(invalid_df['日期'], errors='coerce') invalid_df['格式化'] = invalid_df['安全转换'].dt.strftime('%Y-%m-%d').fillna('无效日期') print("\n安全转换结果:") print(invalid_df) ``` ### 问题3: 日期本地化 ```python # 处理本地化日期 localized_data = { '本地日期': [ '14/10/2021 00:00:00', # 日/月/年 '05/01/2023 00:00:00' ] } localized_df = pd.DataFrame(localized_data) # 指定格式转换 localized_df['格式化'] = pd.to_datetime( localized_df['本地日期'], format='%d/%m/%Y %H:%M:%S' ).dt.strftime('%Y-%m-%d') print("\n本地化日期转换:") print(localized_df) ``` ## 总结 1. **首选方法**:使用 `pd.to_datetime()` 配合 `dt.strftime()` 2. **性能优化**:对于大型数据集,使用矢量化方法 3. **错误处理**:使用 `errors='coerce'` 处理无效日期 4. **格式化**:使用 `strftime('%Y-%m-%d')` 获取日期部分 5. **混合格式**:实现多格式解析逻辑
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值