刚下载的steamsteam下载游戏更新失败败

现在位置:
总共12篇文章博客访问: 870510
博文数量: 538
博客积分: 8915
博客等级: 中将
技术积分: 6114
注册时间:
APP发帖 享双倍积分
IT168企业级官微
微信号:IT168qiye
系统架构师大会
微信号:SACC2013
分类: Android平台
代码基于Android2.3.x版本
Android为Java程序提供了方便的内存泄露信息和工具(如MAT),便于查找。但是,对于纯粹C/C++ 编写的natvie进程,却不那么容易查找内存泄露。传统的C/C++程序可以使用valgrind工具,也可以使用某些代码检查工具。幸运的是,Google的bionic库为我们查找内存泄露提供了一个非常棒的API--get_malloc_leak_info。利用它,我们很容易通过得到backtrace的方式找到涉嫌内存泄露的地方。
代码原理分析
我们可以使用adb shell setprop libc.debug.malloc 1来设置内存的调试等级(debug_level),更详细的等级解释见文件bionic/libc/bionic/malloc_debug_common.c中的注释:
/* Handle to shared library where actual memory allocation is implemented.
* This library is loaded and memory allocation calls are redirected there
* when libc.debug.malloc environment variable contains value other than
* 1 – For memory leak detections.
* 5 – For filling allocated / freed memory with patterns defined by
* CHK_SENTINEL_VALUE, and CHK_FILL_FREE macros.
* 10 – For adding pre-, and post- allocation stubs in order to detect
* buffer overruns.
* Note that emulator’s memory allocation instrumentation is not controlled by
* libc.debug.malloc value, but rather by emulator, started with -memcheck
* option. Note also, that if emulator has started with -memcheck option,
* emulator’s instrumented memory allocation will take over value saved in
* libc.debug.malloc. In other words, if emulator has started with -memcheck
* option, libc.debug.malloc value is ignored.
* Actual functionality for debug levels 1-10 is implemented in
* libc_malloc_debug_leak.so, while functionality for emultor’s instrumented
* allocations is implemented in libc_malloc_debug_qemu.so and can be run inside
* the emulator only.
对于不同的调试等级,内存分配管理函数操作句柄将指向不同的内存分配管理函数。这样,内存的分配和释放,在不同的的调试等级下,将使用不同的函数版本。
详细过程如下:
如下面代码注释所说,在__libc_init例程中会调用malloc_debug_init进行初始化,进而调用malloc_init_impl(在一个进程中,使用pthread_once保证其只被执行一次)
在malloc_init_impl中,会打开对应的C库,解析出函数符号:malloc_debug_initialize(见行366),并执行之(行373)
当debug_level被设置为1、5、10时,打开库”/system/lib/libc_malloc_debug_leak.so”。在文件bionic/libc/bionic/malloc_debug_leak.c中,实现了malloc_debug_initialize,但只为返回0的空函数。若为20,则打开的是:”/system/lib/libc_malloc_debug_qemu.so”
接着,针对不同的debug_level,解析出不同的内存操作函数malloc/free/calloc/realloc/memalign实现:
对于debug_level等级1、5、10的情况,malloc/free/calloc/realloc/memalign各种版本的实现位于文件bionic/libc/bionic/malloc_debug_leak.c中。如debug_level为5时的情况,malloc/free/则是在分配内存时将分配的内存填充为0xeb,释放时填充为0xef:
当debug_level为1调试memory leak时,其实现是打出backtrace:
void* leak_malloc(size_t bytes)
// allocate enough space infront of the allocation to store the pointer for
// the alloc structure. This will making free’ing the structer really fast!
// 1. allocate enough memory and include our header
// 2. set the base pointer to be right after our header
void* base = dlmalloc(bytes + sizeof(AllocationEntry));
if (base != NULL) {
pthread_mutex_lock(&gAllocationsMutex);
intptr_t backtrace[BACKTRACE_SIZE];
size_t numEntries = get_backtrace(backtrace, BACKTRACE_SIZE);
AllocationEntry* header = (AllocationEntry*)
header->entry = record_backtrace(backtrace, numEntries, bytes);
header->guard = GUARD;
// now increment base to point to after our header.
// this should just work since our header is 8 bytes.
base = (AllocationEntry*)base + 1;
pthread_mutex_unlock(&gAllocationsMutex);
该malloc函数在实际分配的bytes字节前额外分配了一块数据用作AllocationEntry。在分配内存成功后,分配了一个拥有32个元素的指针数组,用于存放调用堆栈指针,调用函数get_backtrace将调用堆栈保存起来,也就是将各函数指针保存到数组backtrace中;然后使用record_backtrace记录下该调用堆栈,然后让AllocationEntry的entry成员指向它。函数record_backtrace会通过hash值在全局调用堆栈表gHashTable里查找。若没找到,则创建一项调用堆栈信息,将其加入到全局表中。最后,将base所指向的地方往后移一下,然后它,就是分配的内存地址。
可见,该版本的malloc函数额外记录了调用堆栈的信息。通过在分配的内存块前加一个头的方式,保存了如何查询hash表调用堆栈信息的entry。
再来看一下record_backtrace函数,在分析其代码之前,看一下结构体(文件malloc_debug_common.h):
struct HashEntry {
size_// HashTable中的slots数组索引
HashEntry*//前一项
HashEntry*//后一项,新添加时添加到后面
size_t numE//调用堆栈中的函数指针数量
// fields above “size” are NOT sent to the host
size_//表示该次malloc操作所分配的内存数
size_//调用的次数,即此处的malloc被调用了多少次
intptr_t backtrace[0];//调用堆栈
typedef struct HashTable HashT
struct HashTable {
HashEntry* slots[HASHTABLE_SIZE];//HASHTABLE_SIZE=1543
和在一个进程中,有一个全局的变量gHashTable,用于记录谁最终调用了malloc分配内存的调用堆栈列表。gHashTable的类型是HashTable,其有一个指针,这个指针指向一个slots数组,该数组的最大容量是1543;数组中有多少有效的值由另一个成员count记录。可以通过backtrace和 numEntries得到hash值,再与HASHTABLE_SIZE整除得到HashEntry在该数组中的索引,这样就可以根据自身信息根据hash,快速得到在数组中的索引。
另一个结构体是HashEntry,因其成员存在指向前后的指针,所以它也是个链表,hash值相同将添加到链表的后面。HashEntry第一个成员slot就是自身在数组中的索引,亦即由hash运算而来;最后一项即调用堆栈backtrace[0],里面是函数指针,这个数组具体有多少项则由另一个成员numEntries记录;size表示该次分配的内存的大小;allocations是分配次数,即有多少次同一调用路径。
这两个数据结构关系可由下图表示:
在leak_malloc中调用record_backtrace记录堆栈信息时,先由backtrace和numEntries得到hash值,再整除运算后得到在gHashTable中的数组索引;接着检查是否已经存在该项,即有没有分配了相同内存大小、同一调用路径、记录了相当数量的函数指针的HashEntry。若有,则直接在原有项上的allocations加1,没有则创建新项:为HashEntry结构体分配内存(见行151,注意最后一个成员backtrace需要根据numEntries值来确定其有多少项),然后调用堆栈信息复制给HashEntry最后的一个成员backtrace。最后,还要为整个表格增加计数。
这样record_backtrace函数完成了向全局表中添加backtrace信息的任务:要么新增加一项HashEntry,要么增加索引。
static HashEntry* record_backtrace(intptr_t* backtrace, size_t numEntries, size_t size)
size_t hash = get_hash(backtrace, numEntries);//得到backtrace和numEntries的hash值
size_t slot = hash % HASHTABLE_SIZE;//整除,得到的是HashTable中的HashEntry数组索引
if (size & SIZE_FLAG_MASK) {
debug_log(“malloc_debug: allocation %zx exceeds bit widthn”, size);
if (gMallocLeakZygoteChild)
size |= SIZE_FLAG_ZYGOTE_CHILD;
HashEntry* entry = find_entry(&gHashTable, slot, backtrace, numEntries, size);
//上面一行: 在全局表中搜索该项是否已经存在,即是否该调用路径是否已经被调用过
if (entry != NULL) {
entry->allocations++;//若调用过,则增加计数
} else {//若没有调用,则创建一新项
// create a new entry
entry = (HashEntry*)dlmalloc(sizeof(HashEntry) + numEntries*sizeof(intptr_t));//为该项分配内存,
if (!entry)//接上一行:因HashEntry最后一项是intptr_t backtrace[0];故它是一动态长度,所有numEntries*sizeof(intptr_t)
return NULL;
entry->allocations = 1;
entry->slot =
entry->prev = NULL;
entry->next = gHashTable.slots[slot];
entry->numEntries = numE
entry->size =
memcpy(entry->backtrace, backtrace, numEntries * sizeof(intptr_t));//将backtrace拷贝到entry结构体的后面的内存中
gHashTable.slots[slot] =//将新分配的并经过赋值的一项HashEntry添加到HashTable中的数组中去
if (entry->next != NULL) {
entry->next->prev =
// we just added an entry, increase the size of the hashtable
gHashTable.count++;//增加计数
在leak_free函数中会释放上述全局hash表中的堆栈项(见行550):
void leak_free(void* mem)
if (mem != NULL) {
pthread_mutex_lock(&gAllocationsMutex);
// check the guard to make sure it is valid
AllocationEntry* header = (AllocationEntry*)mem – 1;
if (header->guard != GUARD) {
// could be a memaligned block
if (((void**)mem)[-1] == MEMALIGN_GUARD) {
mem = ((void**)mem)[-2];
header = (AllocationEntry*)mem – 1;
if (header->guard == GUARD || is_valid_entry(header->entry)) {
// decrement the allocations
HashEntry* entry = header->
entry->allocations–;
if (entry->allocations <= 0) {
remove_entry(entry);
dlfree(entry);
// now free the memory!
dlfree(header);
debug_log(“WARNING bad header guard: ’0x%x’! and invalid entry: %pn”,
header->guard, header->entry);
pthread_mutex_unlock(&gAllocationsMutex);
因此,在全局表中剩下的未被释放的项,就是分配了内存但未被释放的调用了malloc的调用堆栈。
get_malloc_leak_info
函数get_malloc_leak_info用于获取内存泄露信息。在分配内存时,记录下调用堆栈,在释放时清除它们。这样,剩下的就很有可能是产生内存泄露的根源。那么如何获取该内存调用堆栈全局hash表呢?在文件malloc_debug_common.c中提供了函数get_malloc_leak_info,可以获取该堆栈信息。
函数get_malloc_leak_info接收5个参数,用于各种存放各种变量的地址,调用结束后,这些变量将得到修改。如其代码注释所说:
*info将指向在该函数中分配的整块内存,这些内存空间大小为overallSize;
整个空间若干小项组成,每项的大小为infoSize,这个小项的数据结构等同于HashEntry中自size成员开始的结构,即第一个成员是malloc分配的内存大小,第二个成员是allocations,即多次有着相同调用堆栈的计数,最后一项是backtrace,共32(BACKTRACE_SIZE)个指针值的空间。因此,*info指向的大内存块包含了共有overallSize/infoSize个小项。注意HashEntry中backtrace数组是按实际数量分配的,而此处则统一按32个分配空间,若不到32个,则后面的值置0;
totalMemory是malloc分配的所有内存的大小;
最后一个参数是backtraceSize,即32(BACKTRACE_SIZE)
函数get_malloc_leak_info首先检查传递进来的变量是否合法,以及全局堆栈中是否有堆栈项:
void get_malloc_leak_info(uint8_t** info, size_t* overallSize,
size_t* infoSize, size_t* totalMemory, size_t* backtraceSize)
// don’t do anything if we have invalid arguments
if (info == NULL || overallSize == NULL || infoSize == NULL ||
totalMemory == NULL || backtraceSize == NULL) {
*totalMemory = 0;
pthread_mutex_lock(&gAllocationsMutex);
if (gHashTable.count == 0) {
*info = NULL;
*overallSize = 0;
*infoSize = 0;
*backtraceSize = 0;
接着查看全局堆栈表中有多少项,然后分配一块内存,用于保存指针,这些指针用于指向gHashTable中的所有HashEntry项,并顺便计数出已分配但未释放的内存总数量totalMemory用于返回给调用者。最后一个参数是调用堆栈中的函数指针个数,实际值为BACKTRACE_SIZE,即32。.
void** list = (void**)dlmalloc(sizeof(void*) * gHashTable.count);
// get the entries into an array to be sorted
int index = 0;
for (i = 0 ; i < HASHTABLE_SIZE ; i++) {//遍历gHashTable全部项
HashEntry* entry = gHashTable.slots[i];
while (entry != NULL) {//有效项放到list中去
list[index] =
*totalMemory = *totalMemory +//计算总分配的内存
((entry->size & ~SIZE_FLAG_MASK) * entry->allocations);
entry = entry->//让entry指向下一个,即相同的slot值
}//经过此for循环,将全局表中所有的堆栈项指针存放到list指向的表中
// XXX: the protocol doesn’t allow variable size for the stack trace (yet)
*infoSize = (sizeof(size_t) * 2) + (sizeof(intptr_t) * BACKTRACE_SIZE);//32个指针值项,
//注意: info前面是两个size_t变量,它们是HashEntry中的size和allocations两个成员,后面是backtrace
*overallSize = *infoSize * gHashTable.//计算所有调用堆栈项所需内存
*backtraceSize = BACKTRACE_SIZE;
最后,为所有调用堆栈项信息分配内存,即info指向的地方;并将gHashTable中的调用堆栈信息(即list表中的HashEntry自其结构体成员size后面的值)拷贝到info所指向的内存中。
// now get A byte array big enough for this
*info = (uint8_t*)dlmalloc(*overallSize);//为所有堆栈项分配内存,包括各项的2个size_t变量
if (*info == NULL) {//分配不成功,没内存了
*overallSize = 0;
goto out_nomem_
qsort((void*)list, gHashTable.count, sizeof(void*), hash_entry_compare);//为列表中的项排序
uint8_t* head = *
const int count = gHashTable.
for (i = 0 ; i < i++) {
HashEntry* entry = list[i];
size_t entrySize = (sizeof(size_t) * 2) + (sizeof(intptr_t) * entry->numEntries);
if (entrySize < *infoSize) {
/* we’re writing less than a full entry, clear out the rest */
memset(head + entrySize, 0, *infoSize – entrySize);//调用堆栈32项中未填满的部分
/* make sure the amount we’re copying doesn’t exceed the limit */
entrySize = *infoS
}//下面的一行将32个指针占用空间加上前面两个size_t变量的值复制到info项中
memcpy(head, &(entry->size), entrySize);//size_t变量分别为size和allocations
head += *infoS//让head指向下一个info所在内存
out_nomem_info:
dlfree(list);
pthread_mutex_unlock(&gAllocationsMutex);
当程序运行结束时,一般来说,内存都应该释放,这时我们可以调用get_malloc_leak_info获取未被释放的调用堆栈项。原理上,这些就是内存泄露的地方。但实际情况可能是,在我们运行get_malloc_leak_info时,某些内存应该保留还不应该释放。
另外,我们有时要检查的进程是守护进程,不会退出。所以有些内存应该一直保持下去,不被释放。这时,我们可以选择某个状态的一个时刻来查看未释放的内存,比如在刚进入时的idle状态时的一个时刻,使用get_malloc_leak_info获取未释放的内存信息,然后在程序执行某些操作结束后返回Idle状态时,再次使用get_malloc_leak_info获取未释放的内存信息。两种信息对比,新多出来的调用堆栈项,就存在涉嫌内存泄露。
使用get_malloc_leak_info函数的样例代码如下:
typedef struct {
size_//分配的内存
size_//重复数
intptr_t *//调用堆栈指针
uint8_t *info = NULL;
size_t overallSize = 0;
size_t infoSize = 0;
size_t totalMemory = 0;
size_t backtraceSize = 0;
get_malloc_leak_info(&info, &overallSize, &infoSize, &totalMemory, &backtraceSize);
LOGI(“returned from get_malloc_leak_info, info=0x%x, overallSize=%d, infoSize=%d, totalMemory=%d, backtraceSize=%d”, (int)info, overallSize, infoSize, totalMemory, backtraceSize);
if (info) {
uint8_t *ptr =
size_t count = overallSize / infoS
snprintf(buffer, SIZE, ” Allocation count %in”, count);
result.append(buffer);
snprintf(buffer, SIZE, ” Total meory %in”, totalMemory);
result.append(buffer);
AllocEntry * entries = new AllocEntry[count];//数组
for (size_t i = 0; i < i++) {让获取的堆栈信息填充到 AllocEntry数组中
// Each entry should be size_t, size_t, intptr_t[backtraceSize]
AllocEntry *e = &entries[i];
e->size = *reinterpret_cast(ptr);
ptr += sizeof(size_t);
e->dups = *reinterpret_cast(ptr);
ptr += sizeof(size_t);
e->backtrace = reinterpret_cast(ptr);
ptr += sizeof(intptr_t) * backtraceS
具体调试步骤:
参考/2010/02/analyze-memory-leak-of-android-native.html
下载其补丁包和python工具包
将代码补丁达到android源码中的frameworks/base下,重新编译生成image,烧进手机板里,这时会在/system/bin/下有个二进制程序memorydumper。该代码补丁包向mediaserver进程中添加一个服务,二进制程序通过Binder IPC使用该服务。该服务使用get_malloc_leak_info获取未释放内存信息。
step1.设置调试等级并重启mediaserver进程
adb shell setprop libc.debug.malloc 1
adb shell ps mediaserver
adb shell kill
它的目的是让mediaserver进程使用leak_malloc的版本。当设置调试等级后,杀死mediaserver进程,android系统将自动重启它。这时,它重新加载libc库,内存分配函数通过handle将使用leak_malloc、leak_free版本。
Step2:在某初始状态下,如在使用“照相机”程序之前,执行memorydumper,记录下此时未释放的内存:
$ adb shell /system/bin/memorydumper
$ adb pull /data/memstatus_.0 .
Step3:执行某些操作,如拍照、录制视频或播放几首歌曲,然后退出这些应用程序;
Step4:再次执行memorydumper,记录下此时未释放的内存;通过比较工具,比较此次和step2中的差异;这些差异就是有内存泄露嫌疑的地方。因为第一得到的未释放的可能就是那个时刻不该释放的,比较就是将它们排除掉。
$ adb pull /data/memstatus_.1 .
$ diff memstatus_.0 memstatus_.1 >diff_0_1
Step5:获取maps文件。根据该文件,可以得到.so库文件所在地址范围空间,用于将调用堆栈函数符号地址解析出来。
$ adb pull /proc//maps your_path
Step5.执行参考链接中的python脚本:
./addr2func.py –root-dir=~/u8500-android-2.3_v4.30 –maps-file=maps –product=u8500 diff._0_1>memleak.backtrace
该脚本将通过分析maps文件得到地址段对应的库文件所占用的地址空间,得到每个调用堆栈的地址对应的库,通过下面的命令,得到对应的经过编译器mangled后的函数名称、源文件及其行号:
[root-dir]/prebuilt/linux-x86/toolchain/arm-eabi-4.4.0/bin/arm-eabi-addr2line -f -e [root-dir]/ /out/target/product/[product]/symbols/[libname] callstack_address
然后使用[root-dir]/prebuilt/linux-x86/toolchain/arm-eabi-4.4.0/bin/arm-eabi-c++filt进行函数的demangle,得到与源码一致的函数名称,使我们更易辨认。
一个例子的snapshot:
下面的截图是第一次使用memorydumper得到的调用堆栈地址:
下面的截图是第二次使用memorydumper得到的调用堆栈地址:
两者进行diff比较后得到的差异:
使用addr2func后得到的调用堆栈:
本文链接地址:
原创文章,版权&所有, 转载随意,但请注明出处。
阅读(1806) | 评论(0) | 转发(0) |
相关热门文章
给主人留下些什么吧!~~
请登录后评论。Vold进程启动_百度文库
两大类热门资源免费畅读
续费一年阅读会员,立省24元!
Vold进程启动
上传于||暂无简介
阅读已结束,如果下载本文需要使用0下载券
想免费下载更多文档?
定制HR最喜欢的简历
下载文档到电脑,查找使用更方便
还剩51页未读,继续阅读
定制HR最喜欢的简历
你可能喜欢1177人阅读
android(11)
一定要打开ddms,否则调试端口是关闭的,就无法在程序刚开始的暂停了。我之前不知道要打开ddms才能用jdb,还以为android系统或者sdk出问题了,重装好几次。汗。
我遇到好几次打开ddms会报错:Could not open Selected VM debug port (8700)
此时,要netstat -ano | find &8700& & 查看占用端口的程序,一般是adb.exe结束它。只有占用端口8700的程序为javaw时候才正常。
2 adb push androidserver /data/local/tmp/
chmod 777 /data/local/tmp/androidserver
/data/local/tmp/androidserver
这里我们把ida的androidserver push到手机上,并以root身份执行。
3 adb forward tcp:23946 tcp:23946
将ida的调试端口进行转发,这样pc端的ida才能连接手机。
4 adb shell am start -D -n com.yaotong.crackme/.MainActivity
这里我们以debug模式启动程序。程序会出现waiting for debugger的调试界面。
5 ida attach target app
这时候我们启动ida并attach这个app的进程。
6 suspend on libary loading
我们在debugger setup里勾选 suspend on library load。然后点击继续。
此时一定要在ida中点击运行!!!!!
7 jdb -connect com.sun.jdi.SocketAttach:hostname=127.0.0.1,port=8700
用jdb将app恢复执行。
8 add breakpoint at JNI_OnLoad
随后程序会在加载libcrackme.so这个so文件的时候停住。这时候ida会出现找不到文件的提示,不用管他,点取消即可。随后就能在modules中看到libcrackme.so这个so文件了,我们点进去,然后在JNI_OnLoad处下个断点,然后点击执行,程序就进入了JNI_OnLoad()这个函数。
PS:有时候你明明在一个函数中却无法F5,这时候你需要先按一下”p”键,程序会将这段代码作为函数分析,然后再按一下”F5”,你就能够看到反汇编的函数了。
步骤引自:
http://drops.wooyun.org/tips/6840
附上一篇文章:
Android逆向之动态调试总结
/thread--1.html
参考知识库
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:8780次
排名:千里之外
原创:28篇
(1)(2)(4)(1)(3)(2)(2)(1)(3)(8)(10)(1)

我要回帖

更多关于 steam下载游戏更新失败 的文章

 

随机推荐