《DEBUG HACKS》中文版笔记(二)

来源:互联网 发布:精通c语言薪水有多少 编辑:程序博客网 时间:2024/06/07 02:53

HACK9 调试时必须的栈知识

下面是学习本章节的示例代码

#include <stdio.h>#include <stdlib.h>#define MAX 1024typedef unsigned long long u64;typedef unsigned int u32;u32 max_addend= MAX;u64 sum_till_MAX(u32 n){    u64 sum;    n++;    sum=n;    if(n<max_addend)        sum+=sum_till_MAX(n);    return sum;}int main(int argc,char **argv){    u64 sum = 0;    if(argc == 2)        max_addend = atoi(argv[1]);    if(max_addend > MAX ||max_addend == 0){        fprintf(stderr,"Invalid\n");        return 1;    }    sum = sum_till_MAX(0);    printf("sum(0..%lu) =%llu\n",max_addend,sum);    return 0;}

使用GDB操作栈帧
假设GDB中进程停止在以下状态
这里写图片描述
用rame命令查看现在选择的栈帧
这里写图片描述
现在选择的帧为0,可以查看该帧内的自动变量sum
这里写图片描述
选择上一层栈帧,可以查看变量
这里写图片描述
这里写图片描述
用up 和 down可以快捷的切换栈帧指向
用带Info命令的frame命令可以看到更详细的栈帧信息
这里写图片描述

HACK10 函数调用时的参数传递方法(x86_64)篇

程序异常结束、与预期行为不一致,这是十分常见的故障。有错误信息的话,只需进行字符串查找就能确定显示该信息的源代码位置,相对比较容易。但是,故障的真正原因有可能在显示错误信息之前很远的地方。例如,某个函数计算处错误的值,以该值为参数调用其他的函数。这种情况下,找出程序出错的位置


第三章 内核调试的准备

HACK 15 Oops信息的解读方法

Oops信息是内核中发生致命错误时输出的内核信息。信息中大致包含了错误概况、加载的模块、寄存器信息、栈跟踪信息等。不同架构和内核版本的显示稍有不同,但大体是一样的。
HACK 27 backtrace无法正确显示
栈破坏有时会导致问题难以分析。调试器的backtrace并非万能钥匙。
HACK 28数组非法访问导致内存破坏
可怀疑是缓冲区溢出的情况
可怀疑是缓冲区溢出的情况之一就是,即使指定了编译选项-g,利用GDB读入core并显示backtrace之后,栈帧中还是没有显示符号名,如下所示。
(gdb)bt
/#0 0x20565c62 in ?? ()
/#1 0x54648731 in ?? ()
/#2 0x23415785 in ?? ()
代码突然跳转到了或者调用者调用了错误的地址0x20565c62,导致了segmentation fault的发生。
(gdb) x/i 0x20565c62
0x20565c62: Cannot access memory at address 0x20565c62
运行地址的改变
那么,是从哪里跳转到和调用了不存在的地址呢?先来整理一下改变程序运行地址的方法。方法基本分为三类。
1. 第一类就是直接指定地址并调用,C语言中if或for语句等进行条件判断时会用到这种方法,调用同一源代码内的函数时也会使用这种方法。
2. 第二类就是指定一块内存区域,其中保存了跳转地址。
3. 第三类方法就是执行ret命令,用于函数结束时返回调用者函数。
如果bug破坏了这些方法用到的值(被错误的地址覆盖),就可能跳转到错误的地址。但是,第一类方法使用的地址很难被破坏,因为地址一般保存在只读空间内。所以,尝试破坏该地址就会产生segmentation fault。此时,core文件中会记录这一瞬间程序计数器的值,因此比较容易分析。
第二类第三类方法使用的地址位于GlobalOffsetTable或栈等可写的空间中,因此即使bug破坏其内容,也无法被检测到。运行时以segmentation fault的形式显露出来。

确定破坏跳转地址值的位置(栈破坏)
已开头栈跟踪为例,我们要寻找错误地写入0x20565c62这个数据的地方。
这种调查中,很重要的是需要怀疑数据是否为字符串的一部分,因为错误地将数据写入地址的典型情况之一就是字符串复制。由于字符串的输入长度很难预测,若缓冲区过小,再加上对输入字符串的长度检查不完善,就可能发生这种情况。

#include <stdio.h>#include <stdlib.h>char names[]="book cat dog building vegetable curry";void func(void){    char buf[5];    strcpy(buf,names);}int main(void){    func();    return 0;}

这里写图片描述


HACK30 malloc()和free()发生故障

错误使用内存相关库函数引起的bug
应用程序,特别是C语言应用程序的bug中,最常见的就是内存相关库函数的错误使用引发的bug,如内存的双重释放,访问分配空间之外的内存等bug。
这里写图片描述
问题似乎出在执行内存释放的库函数free()中,但实际上是使用free()函数的应用程序的问题,而不是库函数free()的问题。
最危险的情况是:程序可能不会受到内存破坏的影响而继续运行。此时可能会产生以下结果。
会在完全没关系的地方产生SIGSEGV。
使用被破坏的数据进行计算,产生错误的结果。


HACK31应用程序停止响应(死锁篇)

示例程序

#include <stdio.h>#include <stdlib.h>#include <pthread.h>pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;int cnt = 0;void cnt_reset(void){    pthread_mutex_lock(&mutex);    cnt = 0;    pthread_mutex_unlock(&mutex);}void *thr(void *arg){    while(1){        pthread_mutex_lock(&mutex);        if(cnt > 2)            cnt_reset();        else            cnt++;        pthread_mutex_unlock(&mutex);        sleep(1);    }}int main (void){    pthread_t tid;    pthread_create(&tid,NULL,thr,NULL);    pthread_join(tid,NULL);    return 0;}

这里写图片描述
ps的结果状态显示S可以怀疑是陷入了死锁。
-L选项可以显示所有线程。
接着用GDB attach到这个进程上,调查哪里在睡眠j
这里写图片描述
这里写图片描述
启动之后只输入bt命令,显示的并不是运行停止的那个线程,而是最初进入的线程backtrace
进入另一个线程查看情况
这里写图片描述


HACK43 使用strace寻找故障原因的线索

#include <stdio.h>#include <stdlib.h>int main(void){    FILE *fp;    fp = fopen("/eea/dasdf","r");    return 0;}

这里写图片描述
attach到进程上

示例程序#include <stdio.h>#include <stdlib.h>int main(void){    while(1){        FILE *fp;        fp = fopen("/eta/taet","r");        if(fp == NULL)            printf("error\n");        else            fclose(fp);        sleep(3);    }    return 0;}

这里写图片描述

0 0