Linux内存管理内存映射以及通过反汇编定位内存错误问题

提到C语言,我们知道C语言和其他高级语言的最大的区别就是C语言是要操作内存的!

     我们需要知道——变量,其实是内存地址的一个抽像名字罢了。在静态编译的程序中,所有的变量名都会在编译时被转成内存地址。机器是不知道我们取的名字的,只知道地址。

    内存的使用时程序设计中需要考虑的重要因素之一,这不仅由于系统内存是有限的(尤其在嵌入式系统中),而且内存分配也会直接影响到程序的效率。因此,我们要对C语言中的内存管理,有个系统的了解。

    在C语言中,定义了4个内存区间:代码区;全局变量和静态变量区;局部变量区即栈区;动态存储区,即堆区;具体如下:

    1、栈区(stack)— 由编译器自动分配释放 ,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。

    2、堆区(heap) — 一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 。注意它与数据结构中的堆是两回事,分配方式倒是类似于链表,呵呵。
    3、全局区(静态区)(static)—全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域, 未初始化的全局变量和未初始化的静态变量在相邻的 另一块区域。 - 程序结束后由系统释放。
   4、常量区 —常量字符串就是放在这里的。 程序结束后由系统释放
   5、程序代码区—存放函数体的二进制代码。

我们来看张图:

首先我们要知道,源代码编译成程序,程序是放在硬盘上的,而非内存里!只有执行时才会被调用到内存中!

我们来看看程序结构,ELF是是Linux的主要可执行文件格式。ELF文件由4部分组成,分别是ELF头(ELF header)、程序头表(Program header table)、节(Section)和节头表(Section header table)。具体如下:

1、Program header描述的是一个段在文件中的位置、大小以及它被放进内存后所在的位置和大小。即要加载的信息;
2、Sections保存着object 文件的信息,从连接角度看:包括指令,数据,符号表,重定位信息等等。在图中,我们可以看到Sections中包括:
      (1)  .text   文本结 存放指令;
      (2)  .rodata   数据结  readonly;
      (3)  .data  数据结 可读可写; 
3、Section头表(section header table)包含了描述文件sections的信息。每个section在这个表中有一个入口;每个入口给出了该section的名字,大小,等等信息。相当于 索引!
     而程序被加载到内存里面,又是如何分布的呢?我们看看上图中:
1、正文和初始化的数据和未初始化的数据就是我们所说的数据段,正文即代码段;
2、正文段上面是常量区,常量区上面是全局变量和静态变量区,二者占据的就是初始化的数据和未初始化的数据那部分;
3、再上面就是堆,动态存储区,这里是上增长;
4、堆上面是栈,存放的是局部变量,就是局部变量所在代码块执行完毕后,这块内存会被释放,这里栈区是下增长;
5、命令行参数就是$0 $1之类的,环境变量什么的前面的文章已经讲过,有兴趣的可以去看看。
 
    我们知道,内存分为动态内存和静态内存,我们先讲静态内存。

一、静态内存

内存管理---存储模型
       存储模型决定了一个变量的内存分配方式和访问特性,在C语言中主要有三个维度来决定:1、存储时期 2、作用域 3、链接

1、存储时期

    存储时期:变量在内存中的保留时间(生命周期)

    存储时期分为两种情况,关键是看变量在程序执行过程中会不会被系统自动回收掉。

 1)  静态存储时期 Static
        在程序执行过程中一旦分配就不会被自动回收。
        通常来说,任何不在函数级别代码块内定义的变量。
        无论是否在代码块内,只要采用static关键字修饰的变量。

 2) 自动存储时期  Automatic
        除了静态存储以外的变量都是自动存储时期的,或者说只要是在代码块内定义的非static的变量,系统会肚脐自动非配和释放内存;
 
2、作用域
      作用域:一个变量在定义该变量的自身文件中的可见性(访问或者引用)
       在C语言中,一共有3中作用域:
1)  代码块作用域
     在代码块中定义的变量都具有该代码的作用域。从这个变量定义地方开始,到这个代码块结束,该变量是可见的;
2)  函数原型作用域
    出现在函数原型中的变量,都具有函数原型作用域,函数原型作用域从变量定义处一直到原型声明的末尾。
3)  文件作用域
    一个在所有函数之外定义的变量具有文件作用域,具有文件作用域的变量从它的定义处到包含该定义的文件结尾处都是可见的;
 
3、链接
      链接:一个变量在组成程序的所有文件中的可见性(访问或者引用);
      C语言中一共有三种不同的链接:
1)  外部链接
      如果一个变量在组成一个程序的所有文件中的任何位置都可以被访问,则称该变量支持外部链接;
2)  内部链接
    如果一个变量只可以在定义其自身的文件中的任何位置被访问,则称该变量支持内部链接。
3)  空链接   
      如果一个变量只是被定义其自身的当前代码块所私有,不能被程序的其他部分所访问,则成该变量支持空链接
我们来看一个代码示例:

#include <stdio.h>
 
int a = 0;// 全局初始化区  
char *p1; //全局未初始化区  
 
int main()  
{  
	int b; //b在栈区
	char s[] = "abc"; //栈  
	char *p2; //p2在栈区
	 
	char *p3 = "123456"; //123456\0在常量区,p3在栈上。  
	static int c =0; //全局(静态)初始化区
	  
	p1 = (char *)malloc(10);  
	p2 = (char *)malloc(20);  //分配得来得10和20字节的区域就在堆区。  
	 
	strcpy(p1, "123456"); //123456\0放在常量区,编译器可能会将它与p3所指向的"123456"优化成一个地方。  
}  

二、动态内存

    当程序运行到需要一个动态分配的变量时,必须向系统申请取得堆中的一块所需大小的存储空间,用于存储该变量。当不在使用该变量时,也就是它的生命结束时,要显示释放它所占用的存储空间,这样系统就能对该空间 进行再次分配,做到重复使用有线的资源。下面介绍动态内存申请和释放的函数。
1.1 malloc 函数
 malloc函数原型:

#include <stdio.h>
void *malloc(size_t size);

size是需要动态申请的内存的字节数。若申请成功,函数返回申请到的内存的起始地址,若申请失败,返回NULL。我们看下面这个例子:

int *get_memory(int n)
{
    int *p;
    p = (int *)malloc(sizeof(int));
    if(p == NULL)
    {
        printf("malloc error\n");
        return p;
    }
 
    memset(p,0,n*sizeof(int));
}

使用该函数时,有下面几点要注意:
1)只关心申请内存的大小;
2)申请的是一块连续的内存。记得一定要写出错判断;
3)显示初始化。即我们不知这块内存中有什么东西,要对其清零;
 
1.2 free函数
    在堆上分配的额内存,需要用free函数显示释放,函数原型如下:

#include <stdlib.h>
void free(void *ptr);

使用free(),也有下面几点要注意:
1)必须提供内存的起始地址;
    调用该函数时,必须提供内存的起始地址,不能够提供部分地址,释放内存中的一部分是不允许的。
2)malloc和free配对使用;
    编译器不负责动态内存的释放,需要程序员显示释放。因此,malloc与free是配对使用的,避免内存泄漏。
free(p);
p = NULL;
p = NULL是必须的,因为虽然这块内存被释放了,但是p仍指向这块内存,避免下次对p的误操作;
3)不允许重复释放
因为这块内存被释放后,可能已另分配,这块区域被别人占用,如果再次释放,会造成数据丢失;

 
2、我们经常将堆和栈相比较:
2.1申请方式  
stack:  由系统自动分配。 例如,声明在函数中一个局部变量 int b; 系统自动在栈中为b开辟空间  
heap:  需要程序员自己申请,并指明大小,在c中malloc函数 ,如p1 = (char *)malloc(10);  
  
2.2  申请后系统的响应  
栈:只要栈的剩余空间大于所申请空间,系统将为程序提供内存,否则将报异常提示栈溢出。  
堆:首先应该知道操作系统有一个记录空闲内存地址的链表,当系统收到程序的申请时,会遍历该链表,寻找第一个空间大于所申请空间的堆结点,然后将该结点从空闲结点链表中删除,并将该结点的空间分配给程序,另外,对于大多数系统,会在这块内存空间中的首地址处记录本次分配的大小,这样,代码中的delete语句才能正确的释放本内存空间。另外,由于找到的堆结点的大小不一定正好等于申请的大小,系统会自动的将多余的那部分重新放入空闲链表中。 

2.3申请大小的限制  
栈:栈是向低地址扩展的数据结构,是一块连续的内存的区域。这句话的意思是栈顶的地址和栈的最大容量是系统预先规定好的,栈的大小是2M(也有的说是1M,总之是一个编译时就确定的常数),如果申请的空间超过栈的剩余空间时,将提示overflow。因此,能从栈获得的空间较小。 
堆:堆是向高地址扩展的数据结构,是不连续的内存区域。这是由于系统是用链表来存储的空闲内存地址的,自然是不连续的,而链表的遍历方向是由低地址向高地址。堆的大小受限于计算机系统中有效的虚拟内存。由此可见,堆获得的空间比较灵活,也比较大。 

2.4申请效率的比较:  
栈由系统自动分配,速度较快。但程序员是无法控制的。  
堆是由new分配的内存,一般速度比较慢,而且容易产生内存碎片,不过用起来最方便。 

2.5堆和栈中的存储内容  
栈: 在函数调用时,第一个进栈的是主函数中后的下一条指令(函数调用语句的下一条可执行语句)的地址,然后是函数的各个参数,在大多数的C编译器中,参数是由右往左入栈的,然后是函数中的局部变量。注意静态变量是不入栈的。  当本次函数调用结束后,局部变量先出栈,然后是参数,最后栈顶指针指向最开始存的地址,也就是主函数中的下一条指令,程序由该点继续运行。 
堆:一般是在堆的头部用一个字节存放堆的大小。堆中的具体内容由程序员安排。 

2.6存取效率的比较 

char s1[] = "aaaaaaaaaaaaaaa";  
char *s2 = "bbbbbbbbbbbbbbbbb";  
aaaaaaaaaaa是在运行时刻赋值的;  
而bbbbbbbbbbb是在编译时就确定的;  
但是,在以后的存取中,在栈上的数组比指针所指向的字符串(例如堆)快。  
比如:  

#include  
void main()  
{  
	char a = 1;  
	char c[] = "1234567890";  
	char *p ="1234567890";  
	a = c[1];  
	a = p[1];  
	return;  
}  

对应的汇编代码 

0: a = c[1];  
00401067 8A 4D F1 mov cl,byte ptr [ebp-0Fh]  
0040106A 88 4D FC mov byte ptr [ebp-4],cl  
11: a = p[1];  
0040106D 8B 55 EC mov edx,dword ptr [ebp-14h]  
00401070 8A 42 01 mov al,byte ptr [edx+1]  
00401073 88 45 FC mov byte ptr [ebp-4],al  

第一种在读取时直接就把字符串中的元素读到寄存器cl中,而第二种则要先把指针值读到edx中,再根据edx读取字符,显然慢了。 


2.7小结:  
   堆和栈的区别可以用如下的比喻来看出:  
   栈就象我们去饭馆里吃饭,只管点菜(发出申请)、付钱、和吃(使用),吃饱了就走,不必理会切菜、洗菜等准备工作和洗碗、刷锅等扫尾工作,他的好处是快捷,但是自由度小。  
   堆就象是自己动手做喜欢吃的菜肴,比较麻烦,但是比较符合自己的口味,而且自由度大。


补充知识点一:

线程和进程各自有什么区别和优劣呢?
    •进程是资源分配的最小单位,线程是程序执行的最小单位。

    •进程有自己的独立地址空间,每启动一个进程,系统就会为它分配地址空间,建立数据表来维护代码段、堆栈段和数据段,这种操作非常昂贵。而线程是共享进程中的数据的,使用相同的地址空间,因此CPU切换一个线程的花费远比进程要小很多,同时创建一个线程的开销也比进程要小很多。

    •线程之间的通信更方便,同一进程下的线程共享全局变量、静态变量等数据,而进程之间的通信需要以通信的方式(IPC)进行。不过如何处理好同步与互斥是编写多线程程序的难点。

    •但是多进程程序更健壮,多线程程序只要有一个线程死掉,整个进程也死掉了,而一个进程死掉并不会对另外一个进程造成影响,因为进程有自己独立的地址空间。


补充知识点二:

    BSS段通常是指用来存放程序中未初始化的全局变量和静态变量的一块内存区域。特点是可读写的,在程序执行之前BSS段会自动清0。
    可执行程序包括BSS段、数据段、代码段(也称文本段)。
    BSS(Block Started by Symbol)通常是指用来存放程序中未初始化的全局变量和静态变量的一块内存区域。特点是:可读写的,在程序执行之前BSS段会自动清0。所以,未初始的全局变量在程序执行之前已经成0了。
    数据段包括初始化的数据和未初始化的数据(BSS)两部分 。BSS段存放的是未初始化的全局变量和静态变量。
    可使用size命令查看可执行文件的段大小信息。如size a.out。

biao@ubuntu:~/test/photo$ size a.out 
   text    data     bss     dec     hex filename
   2876     632       8    3516     dbc a.out
biao@ubuntu:~/test/photo$ 



Linux驱动mmap内存映射

mmap在linux哪里?

什么是mmap?

上图说了,mmap是操作这些设备的一种方法,所谓操作设备,比如IO端口(点亮一个LED)、LCD控制器、磁盘控制器,实际上就是往设备的物理地址读写数据。

但是,由于应用程序不能直接操作设备硬件地址,所以操作系统提供了这样的一种机制——内存映射,把设备地址映射到进程虚拟地址,mmap就是实现内存映射的接口。

操作设备还有很多方法,如ioctl、ioremap

mmap的好处是,mmap把设备内存映射到虚拟内存,则用户操作虚拟内存相当于直接操作设备了,省去了用户空间到内核空间的复制过程,相对IO操作来说,增加了数据的吞吐量。

什么是内存映射?

既然mmap是实现内存映射的接口,那么内存映射是什么呢?看下图

每个进程都有独立的进程地址空间,通过页表和MMU,可将虚拟地址转换为物理地址,每个进程都有独立的页表数据,这可解释为什么两个不同进程相同的虚拟地址,却对应不同的物理地址。

什么是虚拟地址空间?

每个进程都有4G的虚拟地址空间,其中3G用户空间,1G内核空间(linux),每个进程共享内核空间,独立的用户空间,下图形象地表达了这点

驱动程序运行在内核空间,所以驱动程序是面向所有进程的。

用户空间切换到内核空间有两种方法:

(1)系统调用,即软中断

(2)硬件中断

虚拟地址空间里面是什么?

了解了什么是虚拟地址空间,那么虚拟地址空间里面装的是什么?看下图

虚拟空间装的大概是上面那些数据了,内存映射大概就是把设备地址映射到上图的红色段了,暂且称其为“内存映射段”,至于映射到哪个地址,是由操作系统分配的,操作系统会把进程空间划分为三个部分:

(1)未分配的,即进程还未使用的地址

(2)缓存的,缓存在ram中的页

(3)未缓存的,没有缓存在ram中

操作系统会在未分配的地址空间分配一段虚拟地址,用来和设备地址建立映射,至于怎么建立映射,后面再揭晓。

现在大概明白了“内存映射”是什么了,那么内核是怎么管理这些地址空间的呢?任何复杂的理论最终也是通过各种数据结构体现出来的,而这里这个数据结构就是进程描述符。从内核看,进程是分配系统资源(CPU、内存)的载体,为了管理进程,内核必须对每个进程所做的事情进行清楚的描述,这就是进程描述符,内核用task_struct结构体来表示进程,并且维护一个该结构体链表来管理所有进程。该结构体包含一些进程状态、调度信息等上千个成员,我们这里主要关注进程描述符里面的内存描述符(struct mm_struct mm)

内存描述符

具体的结构,请参考下图

现在已经知道了内存映射是把设备地址映射到进程空间地址(注意:并不是所有内存映射都是映射到进程地址空间的,ioremap是映射到内核虚拟空间的,mmap是映射到进程虚拟地址的),实质上是分配了一个vm_area_struct结构体加入到进程的地址空间,也就是说,把设备地址映射到这个结构体,映射过程就是驱动程序要做的事了。

内存映射的实现

以字符设备驱动为例,一般对字符设备的操作都如下框图

而内存映射的主要任务就是实现内核空间中的mmap()函数,先来了解一下字符设备驱动程序的框架

以下是mmap_driver.c的源代码

//所有的模块代码都包含下面两个头文件  
#include <linux/module.h>  
#include <linux/init.h>  
  
#include <linux/types.h> //定义dev_t类型  
#include <linux/cdev.h> //定义struct cdev结构体及相关操作  
#include <linux/slab.h> //定义kmalloc接口  
#include <asm/io.h>//定义virt_to_phys接口  
#include <linux/mm.h>//remap_pfn_range  
#include <linux/fs.h>  
  
#define MAJOR_NUM 990  
#define MM_SIZE 4096  
  
static char driver_name[] = "mmap_driver1";//驱动模块名字  
static int dev_major = MAJOR_NUM;  
static int dev_minor = 0;  
char *buf = NULL;  
struct cdev *cdev = NULL;  
  
static int device_open(struct inode *inode, struct file *file)  
{  
    printk(KERN_ALERT"device open\n");  
    buf = (char *)kmalloc(MM_SIZE, GFP_KERNEL);//内核申请内存只能按页申请,申请该内存以便后面把它当作虚拟设备  
    return 0;  
}  
  
static int device_close(struct inode *indoe, struct file *file)  
{  
    printk("device close\n");  
    if(buf)  
    {  
        kfree(buf);  
    }  
    return 0;  
}  
  
static int device_mmap(struct file *file, struct vm_area_struct *vma)  
{  
    vma->vm_flags |= VM_IO;//表示对设备IO空间的映射  
    vma->vm_flags |= VM_RESERVED;//标志该内存区不能被换出,在设备驱动中虚拟页和物理页的关系应该是长期的,应该保留起来,不能随便被别的虚拟页换出  
    if(remap_pfn_range(vma,//虚拟内存区域,即设备地址将要映射到这里  
                       vma->vm_start,//虚拟空间的起始地址  
                       virt_to_phys(buf)>>PAGE_SHIFT,//与物理内存对应的页帧号,物理地址右移12位  
                       vma->vm_end - vma->vm_start,//映射区域大小,一般是页大小的整数倍  
                       vma->vm_page_prot))//保护属性,  
    {  
        return -EAGAIN;  
    }  
    return 0;  
}  
  
static struct file_operations device_fops =  
{  
    .owner = THIS_MODULE,  
    .open  = device_open,  
    .release = device_close,  
    .mmap = device_mmap,  
};  
  
static int __init char_device_init( void )  
{  
    int result;  
    dev_t dev;//高12位表示主设备号,低20位表示次设备号  
    printk(KERN_ALERT"module init2323\n");  
    printk("dev=%d", dev);  
    dev = MKDEV(dev_major, dev_minor);  
    cdev = cdev_alloc();//为字符设备cdev分配空间  
    printk(KERN_ALERT"module init\n");  
    if(dev_major)  
    {  
        result = register_chrdev_region(dev, 1, driver_name);//静态分配设备号  
        printk("result = %d\n", result);  
    }  
    else  
    {  
        result = alloc_chrdev_region(&dev, 0, 1, driver_name);//动态分配设备号  
        dev_major = MAJOR(dev);  
    }  
      
    if(result < 0)  
    {  
        printk(KERN_WARNING"Cant't get major %d\n", dev_major);  
        return result;  
    }  
      
      
    cdev_init(cdev, &device_fops);//初始化字符设备cdev  
    cdev->ops = &device_fops;  
    cdev->owner = THIS_MODULE;  
      
    result = cdev_add(cdev, dev, 1);//向内核注册字符设备  
    printk("dffd = %d\n", result);  
    return 0;  
}  
  
static void __exit char_device_exit( void )  
{  
    printk(KERN_ALERT"module exit\n");  
    cdev_del(cdev);  
    unregister_chrdev_region(MKDEV(dev_major, dev_minor), 1);  
}  
  
module_init(char_device_init);//模块加载  
module_exit(char_device_exit);//模块退出  
  
MODULE_LICENSE("GPL");  
MODULE_AUTHOR("ChenShengfa"); 

下面是测试代码test_mmap.c

#include <stdio.h>  
#include <fcntl.h>  
#include <sys/mman.h>  
#include <stdlib.h>  
#include <string.h>  
  
int main( void )  
{  
    int fd;  
    char *buffer;  
    char *mapBuf;  
    fd = open("/dev/mmap_driver", O_RDWR);//打开设备文件,内核就能获取设备文件的索引节点,填充inode结构  
    if(fd<0)  
    {  
        printf("open device is error,fd = %d\n",fd);  
        return -1;  
    }  
    /*测试一:查看内存映射段*/  
    printf("before mmap\n");  
    sleep(15);//睡眠15秒,查看映射前的内存图cat /proc/pid/maps  
    buffer = (char *)malloc(1024);  
    memset(buffer, 0, 1024);  
    mapBuf = mmap(NULL, 1024, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);//内存映射,会调用驱动的mmap函数  
    printf("after mmap\n");  
    sleep(15);//睡眠15秒,在命令行查看映射后的内存图,如果多出了映射段,说明映射成功  
      
    /*测试二:往映射段读写数据,看是否成功*/  
    strcpy(mapBuf, "Driver Test");//向映射段写数据  
    memset(buffer, 0, 1024);  
    strcpy(buffer, mapBuf);//从映射段读取数据  
    printf("buf = %s\n", buffer);//如果读取出来的数据和写入的数据一致,说明映射段的确成功了  
      
      
    munmap(mapBuf, 1024);//去除映射  
    free(buffer);  
    close(fd);//关闭文件,最终调用驱动的close  
    return 0;  
}  

下面是makefile文件

ifneq ($(KERNELRELEASE),)  
  
obj-m := mmap_driver.o  
  
else  
KDIR := /lib/modules/3.2.0-52-generic/build  
  
all:  
    make -C $(KDIR) M=$(PWD) modules  
clean:  
    rm -f *.ko *.o *.mod.o *.mod.c *~ *.symvers *.order  
  
endif  

下面命令演示一下驱动程序的编译、安装、测试过程(注:其他用户在mknod之后还需要chmod改变权限)

# make    //编译驱动

# insmod mmap_driver.ko    //安装驱动

# mknod /dev/mmap_driver c 999 0    //创建设备文件

# gcc test_mmap.c -o test.o    //编译应用程序

# ./test.o    //运行应用程序来测试驱动程序


拓展:

关于这个过程,涉及一些术语

(1)设备文件:linux中对硬件虚拟成设备文件,对普通文件的各种操作均适用于设备文件

(2)索引节点:linux使用索引节点来记录文件信息(如文件长度、创建修改时间),它存储在磁盘中,读入内存后就是一个inode结构体,文件系统维护了一个索引节点的数组,每个元素都和文件或者目录一一对应。

(3)主设备号:如上面的999,表示设备的类型,比如该设备是lcd还是usb等

(4)次设备号:如上面的0,表示该类设备上的不同设备

(5)文件(普通文件或设备文件)的三个结构

        ①文件操作:struct file_operations

        ②文件对象:struct file

        ③文件索引节点:struct inode

关于驱动程序中内存映射的实现,先了解一下open和close的流程

(1)设备驱动open流程

①应用程序调用open("/dev/mmap_driver", O_RDWR);

②Open就会通过VFS找到该设备的索引节点(inode),mknod的时候会根据设备号把驱动程序的file_operations结构填充到索引节点中(关于mknod /dev/mmap_driver c 999 0,这条指令创建了设备文件,在安装驱动(insmod)的时候,会运行驱动程序的初始化程序(module_init),在初始化程序中,会注册它的主设备号到系统中(cdev_add),如果mknod时的主设备号999在系统中不存在,即和注册的主设备号不同,则上面的指令会执行失败,就创建不了设备文件)

③然后根据设备文件的索引节点中的file_operations中的open指针,就调用驱动的open方法了。

④生成一个文件对象files_struct结构,系统维护一个files_struct的链表,表示系统中所有打开的文件

⑤返回文件描述符fd,把fd加入到进程的文件描述符表中

(2)设备驱动close流程

应用程序调用close(fd),最终可调用驱动的close,为什么根据一个简单的int型fd就可以找到驱动的close函数?这就和上面说的三个结构(struct file_operations、struct file、struct inode)息息相关了,假如fd = 3

(3)设备驱动mmap流程

由open和close得知,同理,应用程序调用mmap最终也会调用到驱动程序中mmap方法

①应用程序test.mmap.c中mmap函数

void* mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);

addr:映射后虚拟地址的起始地址,通常为NULL,内核自动分配

length:映射区的大小

prot:页面访问权限(PROT_READ、PROT_WRITE、PROT_EXEC、PROT_NONE)

flags:参考网络资料

fd:文件描述符

offset:文件映射开始偏移量

②驱动程序的mmap_driver.c中mmap函数

上面说了,mmap的主要工作是把设备地址映射到进程虚拟地址,也即是一个vm_area_struct的结构体,这里说的映射,是一个很悬的东西,那它在程序中的表现是什么呢?——页表,没错,就是页表,映射就是要建立页表。进程地址空间就可以通过页表(软件)和MMU(硬件)映射到设备地址上了

virt_to_phys(buf),buf是在open时申请的地址,这里使用virt_to_phys把buf转换成物理地址,是模拟了一个硬件设备,即把虚拟设备映射到虚拟地址,在实际中可以直接使用物理地址。

总结

①从以上看到,内核各个模块错综复杂、相互交叉

②单纯一个小小驱动模块,就涉及了进程管理(进程地址空间)、内存管理(页表与页帧映射)、虚拟文件系统(structfile、structinode)

③并不是所有设备驱动都可以使用mmap来映射,比如像串口和其他面向流的设备,并且必须按照页大小进行映射。



 ELF文件格式分析

一、概述

1.ELF全称Executable and Linkable Format,可执行连接格式,ELF格式的文件用于存储Linux程序。ELF文件(目标文件)格式主要三种:

  • 可重定向文件:文件保存着代码和适当的数据,用来和其他的目标文件一起来创建一个可执行文件或者是一个共享目标文件。(目标文件或者静态库文件,即linux通常后缀为.a和.o的文件)
  • 可执行文件:文件保存着一个用来执行的程序。(例如bash,gcc等)
  • 共享目标文件:共享库。文件保存着代码和合适的数据,用来被下连接编辑器和动态链接器链接。(linux下后缀为.so的文件。)
    目标文件既要参与程序链接又要参与程序执行:

一般的 ELF 文件包括三个索引表:ELF header,Program header table,Section header table。

  • ELF header:在文件的开始,保存了路线图,描述了该文件的组织情况。
  • Program header table:告诉系统如何创建进程映像。用来构造进程映像的目标文件必须具有程序头部表,可重定位文件不需要这个表。
  • Section header table:包含了描述文件节区的信息,每个节区在表中都有一项,每一项给出诸如节区名称、节区大小这类信息。用于链接的目标文件必须包含节区头部表,其他目标文件可以有,也可以没有这个表。

二、分析ELF文件头(ELF header)

  • 进入终端输入:cd /usr/include 进入include文件夹后查看elf.h文件,查看ELF的文件头包含整个文件的控制结构
    image001.png-409.5kB

  • 写一个小程序(hello 20135328)进行编译,生成hello可执行文件。
    使用‘readelf –a hello’命令,都得到下面的ELF Header头文件的信息,如下图:
    image005.png-90.1kB
    image007.png-336.2kB

  • 通过上图信息,可以得出Elf Header的Size为52bytes,所以可以使用hexdump工具将头文件的16进制表打开。
    如下图使用:‘hexdump -x hello -n 52 ’命令来查看hello文件头的16进制表(前52bytes)对格式进行分析。
    QQ截图20160601195715.png-135.3kB

  • 第一行
    • 对应e_ident[EI_NIDENT]。实际表示内容为7f454c46010100010000000000000000,前四个字节7f454c46(0x45,0x4c,0x46是'e','l','f'对应的ascii编码)是一个魔数,表示这是一个ELF对象。
    • 接下来的一个字节01表示是一个32位对象,接下来的一个字节01表示是小端法表示,再接下来的一个字节01表示文件头版本。剩下的默认都设置为0.
  • 第二行
    • e_type值为0x0002,表示是一个可执行文件
    • 。e_machine值为0x003e,表示是Advanced Micro Devices X86-64处理器体系结构。
    • e_version值为0x00000001,表示是当前版本。
    • e_entry值为0x 08048320,表示入口点。
  • 第三行
    • e_phof f值为0x1178,表示程序头表。
    • e_shoff值为0x0034,表示段表的偏移地址。
  • 第四行
    • e_flags值为0x001e,表示未知处理器特定标志。
    • e_ehsize值为0x0034,表示elf文件头大小(正好是52bytes)。
    • e_phentsize表示一个program header表中的入口的长度,值为0x0020。
    • e_phnum的值为0x0009,给出program header表中的入口数目。
    • e_shentsize值为0x0028表示段头大小为40个字节。e_shnum值为0x001e,表示段表入口有30个。
    • e_shstrndx值为0x001b,表示段名串表的在段表中的索引号。

      三、通过文件头找到section header table,理解其内容

  • file elf显示生成的目标文件hello的类型
  • elf是一个可执行文件。输入:ls –l hello查看hello的大小:
    image013.png-125kB
  • 如图可知,hello大小为7336字节。
    输入:hexdump –x hello来用16进制的数字来显示hello的内容
    (其中,第二列是16进制表示的偏移地址)
    QQ截图20160601193548.png-608.2kB
  • 输入:objdump –x hello来显示hello中各个段以及符号表的相关信息:
    image015.png-442.7kB
  • 输入:readelf –a hello来查看各个段信息:
  • ELF文件头信息:
    image017.png-402.2kB
  • 段表Section header table:
    image025.png-597.4kB
  • 符号表 Symbol table:
    image019.png-549.3kB
    image021.png-593.1kB
    image023.png-78.5kB

    四、通过section header table找到各section

    在一个ELF文件中有一个section header table,通过它我们可以定位到所有的 section,而 ELF header 中的e_shoff 变量就是保存 section header table 入口对文件头的偏移量。而每个 section 都会对应一个 section header ,所以只要在 section header table 中找到每个 section header,就可以通过 section header 找到你想要的 section。

下面以可执行文件hello为例,以保存代码段的 section 为例来讲解读取某个section 的过程。
使用‘vi /usr/include/elf.h ’命令查看Sections Header的结构体:
image027.png-540.1kB

由上面分析可知,section headers table中的每一个section header所占的size均为64字节,ELF header得到了e_shoff变量的值为0X0034,也就是table入口的偏移量,通过看e_shnum值为0x001e,表示段表入口有30个。
所以从0x00000034开始有30个段,每个段占40个字节大小,输入 hexdump hello查看:
image029.png-333.8kB

  • 第一个段,其中内容全部为0,所以不表示任何段。
  • 第二个段,为.interp段

  • 第三个段,为.note.ABI-tag段

  • 第四个段,为.note.gnu.build-i段

  • 第五个段,为.gnu.hash段

.......

  • 第十四个段,为.text段

  • 第十六个段,为.rodata段

  • 第二十五个段,为.data段

  • 第二十六个段,为.bss段

  • 第二十九个段, 为.symtab段

  • 第三十个段, 为.strtab段

我们用readelf 命令去查看.text这个 section 中的内容,
输入readelf –x 13 hello,(.text前面的标号为13)对13索引号的.text的section的内容进行查看:
image031.png-566.3kB

下面用 hexdump 的方法去读取.text这个 section 中的内容,通过看section header中.text中offset和size分别是0x320和0x192
输入 hexdump –C hello
找到320后的192个
image033.png-159.3kB
image035.png-126.1kB
得到了和上面的readelf得到的相同。
使用下面命令对hello的文本段(.text)进行反汇编:
objdump –d hello 得到如下图:
image037.png-404.4kB
image039.png-547kB
可以看出,使用反汇编的16进制数据和前面查找到的是相同的。

五、理解常见.text .strtab .symtab .rodata等section

image025.png-597.4kB

  1. text section是可执行指令的集合,.data和.text都是属于PROGBITS类型的section,这是将来要运行的程序与代码。查询段表可知.text section的位偏移为0x0000320,size为0x0000192。
  2. strtab section是属于STRTAB类型的section,可以在文件中看到,它存着字符串,储存着符号的名字。位偏移为0x000106f,size为0x0000106
  3. symtab section存放所有section中定义的符号名字,比如“data_items”,“start_loop”。 .symtab section是属于SYMTAB类型的section,它描述了.strtab中的符号在“内存”中对应的“内存地址”。 位偏移为0x0001628,size为0x0000430。
  4. rodata section,ro代表read only。位偏移为0x000050c,size为0x00000b0。


readelf命令和ELF文件详解 

ELF(Executable and Linking Format)是一个定义了目标文件内部信息如何组成和组织的文件格式。内核会根据这些信息加载可执行文件,内核根据这些信息可以知道从文件哪里获取代码,从哪里获取初始化数据,在哪里应该加载共享库,等信息。 
ELF文件有下面三种类型: 
1.目标文件

$ gcc -c test.c 
得到的test.o就是目标文件,目标文件通过链接可生成可执行文件。 
静态库其实也算目标文件,静态库是通过ar命令将目标打包为.a文件。 
如:ar crv libtest.a test.o

2.可执行文件 
$gcc -o test test.c 
得到的test文件就是可执行的二进制文件。

3.共享库 
$ gcc test.c -fPIC -shared -o libtest.so 
得到的文件listtest.so就是共享库。

可以通过readelf来区分上面三种类型的ELF文件,每种类型文件的头部信息是不一样的。 
$readelf -h test.o 
目标文件

ELF Header:
  Magic:   7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 
  Class:                             ELF64
  Data:                              2's complement, little endian
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI Version:                       0
  Type:                              REL (Relocatable file)
  Machine:                           Advanced Micro Devices X86-64
  Version:                           0x1
  Entry point address:               0x0
  Start of program headers:          0 (bytes into file)
  Start of section headers:          456 (bytes into file)
  Flags:                             0x0
  Size of this header:               64 (bytes)
  Size of program headers:           0 (bytes)
  Number of program headers:         0
  Size of section headers:           64 (bytes)
  Number of section headers:         13
  Section header string table index: 10

$readelf -h test 

可执行文件

ELF Header:
  Magic:   7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 
  Class:                             ELF64
  Data:                              2's complement, little endian
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI Version:                       0
  Type:                              EXEC (Executable file)
  Machine:                           Advanced Micro Devices X86-64
  Version:                           0x1
  Entry point address:               0x400420
  Start of program headers:          64 (bytes into file)
  Start of section headers:          2696 (bytes into file)
  Flags:                             0x0
  Size of this header:               64 (bytes)
  Size of program headers:           56 (bytes)
  Number of program headers:         8
  Size of section headers:           64 (bytes)
  Number of section headers:         30
  Section header string table index: 27

$readelf -h libtest.so 
共享库

ELF Header:
  Magic:   7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 
  Class:                             ELF64
  Data:                              2's complement, little endian
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI Version:                       0
  Type:                              DYN (Shared object file)
  Machine:                           Advanced Micro Devices X86-64
  Version:                           0x1
  Entry point address:               0x570
  Start of program headers:          64 (bytes into file)
  Start of section headers:          2768 (bytes into file)
  Flags:                             0x0
  Size of this header:               64 (bytes)
  Size of program headers:           56 (bytes)
  Number of program headers:         6
  Size of section headers:           64 (bytes)
  Number of section headers:         29
  Section header string table index: 26

下面是test.c文件内容:

#include<stdio.h>

int global_data = 4;
int global_data_2;
int main(int argc, char **argv)
{ 
    int local_data = 3; 

    printf("Hello World\n"); 
    printf("global_data = %d\n", global_data); 
    printf("global_data_2 = %d\n", global_data_2); 
    printf("local_data = %d\n", local_data); 

    return (0);
}

$gcc -o test test.c 
生成可执行文件test,然后使用readelf对其进行分析。

$readelf -h test 
下面是输出结果:

ELF Header:
  Magic:   7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 
  Class:                             ELF64
  Data:                              2's complement, little endian
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI Version:                       0
  Type:                              EXEC (Executable file)
  Machine:                           Advanced Micro Devices X86-64
  Version:                           0x1
  Entry point address:               0x400420
  Start of program headers:          64 (bytes into file)
  Start of section headers:          2696 (bytes into file)
  Flags:                             0x0
  Size of this header:               64 (bytes)
  Size of program headers:           56 (bytes)
  Number of program headers:         8
  Size of section headers:           64 (bytes)
  Number of section headers:         30
  Section header string table index: 27

上面的信息可以告诉我们什么信息? 
1.根据Class、Type和Machine,可以知道该文件在X86-64位机器上生成的64位可执行文件。 
2.根据Entry point address,可以知道当该程序启动时从虚拟地址0x400420处开始运行。这个地址并不是main函数的地址,而是_start函数的地址,_start由链接器创建,_start是为了初始化程序。通过这个命令可以看到_start函数,objdump -d -j .text test 
3.根据Number of program headers,可以知道该程序有8个段。 
4.根据Number of section headers,可以知道该程序有30个区。 
区中存储的信息是用来链接使用的,主要包括:程序代码、程序数据(变量)、重定向信息等。比如:Code section保存的是代码,data section保存的是初始化或未初始化的数据,等等。

Linux内核无法以区的概念来识别可执行文件。内核使用包括连续页的VMA(virtual memory area)来识别进程。在每个VMA中可能映射了一个或多个区。每个VMA代表一个ELF文件的段。 
那么,内核如何知道哪个区属于某个VMA(段)?映射关系保存在Program Header Table(PHT)中。

下面查看区的内容: 

$readelf -S test

There are 30 section headers, starting at offset 0xa88:

Section Headers:
  [Nr] Name              Type             Address           Offset
       Size              EntSize          Flags  Link  Info  Align
  [ 0]                   NULL             0000000000000000  00000000
       0000000000000000  0000000000000000           0     0     0
  [ 1] .interp           PROGBITS         0000000000400200  00000200
       000000000000001c  0000000000000000   A       0     0     1
  [ 2] .note.ABI-tag     NOTE             000000000040021c  0000021c
       0000000000000020  0000000000000000   A       0     0     4
  [ 3] .note.gnu.build-i NOTE             000000000040023c  0000023c
       0000000000000024  0000000000000000   A       0     0     4
  [ 4] .gnu.hash         GNU_HASH         0000000000400260  00000260
       000000000000001c  0000000000000000   A       5     0     8
  [ 5] .dynsym           DYNSYM           0000000000400280  00000280
       0000000000000078  0000000000000018   A       6     1     8
  [ 6] .dynstr           STRTAB           00000000004002f8  000002f8
       0000000000000044  0000000000000000   A       0     0     1
  [ 7] .gnu.version      VERSYM           000000000040033c  0000033c
       000000000000000a  0000000000000002   A       5     0     2
  [ 8] .gnu.version_r    VERNEED          0000000000400348  00000348
       0000000000000020  0000000000000000   A       6     1     8
  [ 9] .rela.dyn         RELA             0000000000400368  00000368
       0000000000000018  0000000000000018   A       5     0     8
  [10] .rela.plt         RELA             0000000000400380  00000380
       0000000000000048  0000000000000018   A       5    12     8
  [11] .init             PROGBITS         00000000004003c8  000003c8
       0000000000000018  0000000000000000  AX       0     0     4
  [12] .plt              PROGBITS         00000000004003e0  000003e0
       0000000000000040  0000000000000010  AX       0     0     4
  [13] .text             PROGBITS         0000000000400420  00000420
       0000000000000238  0000000000000000  AX       0     0     16
  [14] .fini             PROGBITS         0000000000400658  00000658
       000000000000000e  0000000000000000  AX       0     0     4
  [15] .rodata           PROGBITS         0000000000400668  00000668
       0000000000000053  0000000000000000   A       0     0     8
  [16] .eh_frame_hdr     PROGBITS         00000000004006bc  000006bc
       0000000000000024  0000000000000000   A       0     0     4
  [17] .eh_frame         PROGBITS         00000000004006e0  000006e0
       000000000000007c  0000000000000000   A       0     0     8
  [18] .ctors            PROGBITS         0000000000600760  00000760
       0000000000000010  0000000000000000  WA       0     0     8
  [19] .dtors            PROGBITS         0000000000600770  00000770
       0000000000000010  0000000000000000  WA       0     0     8
  [20] .jcr              PROGBITS         0000000000600780  00000780
       0000000000000008  0000000000000000  WA       0     0     8
  [21] .dynamic          DYNAMIC          0000000000600788  00000788
       0000000000000190  0000000000000010  WA       6     0     8
  [22] .got              PROGBITS         0000000000600918  00000918
       0000000000000008  0000000000000008  WA       0     0     8
  [23] .got.plt          PROGBITS         0000000000600920  00000920
       0000000000000030  0000000000000008  WA       0     0     8
  [24] .data             PROGBITS         0000000000600950  00000950
       0000000000000008  0000000000000000  WA       0     0     4
  [25] .bss              NOBITS           0000000000600958  00000958
       0000000000000018  0000000000000000  WA       0     0     8
  [26] .comment          PROGBITS         0000000000000000  00000958
       000000000000002c  0000000000000001  MS       0     0     1
  [27] .shstrtab         STRTAB           0000000000000000  00000984
       00000000000000fe  0000000000000000           0     0     1
  [28] .symtab           SYMTAB           0000000000000000  00001208
       0000000000000648  0000000000000018          29    46     8
  [29] .strtab           STRTAB           0000000000000000  00001850
       000000000000021e  0000000000000000           0     0     1
Key to Flags:
  W (write), A (alloc), X (execute), M (merge), S (strings)
  I (info), L (link order), G (group), x (unknown)
  O (extra OS processing required) o (OS specific), p (processor specific)

.text区存储的是程序的代码(二进制指令),该区的标志为X表示可执行。

下面使用objdump反汇编查看.text的内容: 
$objdump -d -j .text test 
-d选项告诉objdump反汇编机器码,-j选项告诉objdump只关心.text区。

test:     file format elf64-x86-64


Disassembly of section .text:

0000000000400420 <_start>:
  400420:       31 ed                   xor    %ebp,%ebp
  400422:       49 89 d1                mov    %rdx,%r9
  400425:       5e                      pop    %rsi
  400426:       48 89 e2                mov    %rsp,%rdx
  400429:       48 83 e4 f0             and    $0xfffffffffffffff0,%rsp
  40042d:       50                      push   %rax
  40042e:       54                      push   %rsp
  40042f:       49 c7 c0 80 05 40 00    mov    $0x400580,%r8
  400436:       48 c7 c1 90 05 40 00    mov    $0x400590,%rcx
  40043d:       48 c7 c7 04 05 40 00    mov    $0x400504,%rdi
  400444:       e8 c7 ff ff ff          callq  400410 <__libc_start_main@plt>
  400449:       f4                      hlt    
  40044a:       90                      nop
  40044b:       90                      nop

000000000040044c <call_gmon_start>:
  40044c:       48 83 ec 08             sub    $0x8,%rsp
  400450:       48 8b 05 c1 04 20 00    mov    0x2004c1(%rip),%rax        # 600918 <_DYNAMIC+0x190>
  400457:       48 85 c0                test   %rax,%rax
  40045a:       74 02                   je     40045e <call_gmon_start+0x12>
  40045c:       ff d0                   callq  *%rax
  40045e:       48 83 c4 08             add    $0x8,%rsp
  400462:       c3                      retq   
  400463:       90                      nop
  400464:       90                      nop
  400465:       90                      nop
  400466:       90                      nop
  400467:       90                      nop
  400468:       90                      nop
  400469:       90                      nop
  40046a:       90                      nop
  40046b:       90                      nop
  40046c:       90                      nop
  40046d:       90                      nop
  40046e:       90                      nop
  40046f:       90                      nop

0000000000400470 <__do_global_dtors_aux>:
  400470:       55                      push   %rbp
  400471:       48 89 e5                mov    %rsp,%rbp
  400474:       53                      push   %rbx
  400475:       48 83 ec 08             sub    $0x8,%rsp
  400479:       80 3d d8 04 20 00 00    cmpb   $0x0,0x2004d8(%rip)        # 600958 <__bss_start>
  400480:       75 4b                   jne    4004cd <__do_global_dtors_aux+0x5d>
  400482:       bb 78 07 60 00          mov    $0x600778,%ebx
  400487:       48 8b 05 d2 04 20 00    mov    0x2004d2(%rip),%rax        # 600960 <dtor_idx.6349>
  40048e:       48 81 eb 70 07 60 00    sub    $0x600770,%rbx
  400495:       48 c1 fb 03             sar    $0x3,%rbx
  400499:       48 83 eb 01             sub    $0x1,%rbx
  40049d:       48 39 d8                cmp    %rbx,%rax
  4004a0:       73 24                   jae    4004c6 <__do_global_dtors_aux+0x56>
  4004a2:       66 0f 1f 44 00 00       nopw   0x0(%rax,%rax,1)
  4004a8:       48 83 c0 01             add    $0x1,%rax
  4004ac:       48 89 05 ad 04 20 00    mov    %rax,0x2004ad(%rip)        # 600960 <dtor_idx.6349>
  4004b3:       ff 14 c5 70 07 60 00    callq  *0x600770(,%rax,8)
  4004ba:       48 8b 05 9f 04 20 00    mov    0x20049f(%rip),%rax        # 600960 <dtor_idx.6349>
  4004c1:       48 39 d8                cmp    %rbx,%rax
  4004c4:       72 e2                   jb     4004a8 <__do_global_dtors_aux+0x38>
  4004c6:       c6 05 8b 04 20 00 01    movb   $0x1,0x20048b(%rip)        # 600958 <__bss_start>
  4004cd:       48 83 c4 08             add    $0x8,%rsp
  4004d1:       5b                      pop    %rbx
  4004d2:       c9                      leaveq 
  4004d3:       c3                      retq   
  4004d4:       66 66 66 2e 0f 1f 84    data32 data32 nopw %cs:0x0(%rax,%rax,1)
  4004db:       00 00 00 00 00 

00000000004004e0 <frame_dummy>:
  4004e0:       48 83 3d 98 02 20 00    cmpq   $0x0,0x200298(%rip)        # 600780 <__JCR_END__>
  4004e7:       00 
  4004e8:       55                      push   %rbp
  4004e9:       48 89 e5                mov    %rsp,%rbp
  4004ec:       74 12                   je     400500 <frame_dummy+0x20>
  4004ee:       b8 00 00 00 00          mov    $0x0,%eax
  4004f3:       48 85 c0                test   %rax,%rax
  4004f6:       74 08                   je     400500 <frame_dummy+0x20>
  4004f8:       bf 80 07 60 00          mov    $0x600780,%edi
  4004fd:       c9                      leaveq 
  4004fe:       ff e0                   jmpq   *%rax
  400500:       c9                      leaveq 
  400501:       c3                      retq   
  400502:       90                      nop
  400503:       90                      nop

0000000000400504 <main>:
  400504:       55                      push   %rbp
  400505:       48 89 e5                mov    %rsp,%rbp
  400508:       48 83 ec 20             sub    $0x20,%rsp
  40050c:       89 7d ec                mov    %edi,-0x14(%rbp)
  40050f:       48 89 75 e0             mov    %rsi,-0x20(%rbp)
  400513:       c7 45 fc 03 00 00 00    movl   $0x3,-0x4(%rbp)
  40051a:       bf 78 06 40 00          mov    $0x400678,%edi
  40051f:       e8 dc fe ff ff          callq  400400 <puts@plt>
  400524:       8b 15 2a 04 20 00       mov    0x20042a(%rip),%edx        # 600954 <global_data>
  40052a:       b8 84 06 40 00          mov    $0x400684,%eax
  40052f:       89 d6                   mov    %edx,%esi
  400531:       48 89 c7                mov    %rax,%rdi
  400534:       b8 00 00 00 00          mov    $0x0,%eax
  400539:       e8 b2 fe ff ff          callq  4003f0 <printf@plt>
  40053e:       8b 15 24 04 20 00       mov    0x200424(%rip),%edx        # 600968 <global_data_2>
  400544:       b8 96 06 40 00          mov    $0x400696,%eax
  400549:       89 d6                   mov    %edx,%esi
  40054b:       48 89 c7                mov    %rax,%rdi
  40054e:       b8 00 00 00 00          mov    $0x0,%eax
  400553:       e8 98 fe ff ff          callq  4003f0 <printf@plt>
  400558:       b8 aa 06 40 00          mov    $0x4006aa,%eax
  40055d:       8b 55 fc                mov    -0x4(%rbp),%edx
  400560:       89 d6                   mov    %edx,%esi
  400562:       48 89 c7                mov    %rax,%rdi
  400565:       b8 00 00 00 00          mov    $0x0,%eax
  40056a:       e8 81 fe ff ff          callq  4003f0 <printf@plt>
  40056f:       b8 00 00 00 00          mov    $0x0,%eax
  400574:       c9                      leaveq 
  400575:       c3                      retq   
  400576:       90                      nop
  400577:       90                      nop
  400578:       90                      nop
  400579:       90                      nop
  40057a:       90                      nop
  40057b:       90                      nop
  40057c:       90                      nop
  40057d:       90                      nop
  40057e:       90                      nop
  40057f:       90                      nop

0000000000400580 <__libc_csu_fini>:
  400580:       f3 c3                   repz retq 
  400582:       66 66 66 66 66 2e 0f    data32 data32 data32 data32 nopw %cs:0x0(%rax,%rax,1)
  400589:       1f 84 00 00 00 00 00 

0000000000400590 <__libc_csu_init>:
  400590:       48 89 6c 24 d8          mov    %rbp,-0x28(%rsp)
  400595:       4c 89 64 24 e0          mov    %r12,-0x20(%rsp)
  40059a:       48 8d 2d bb 01 20 00    lea    0x2001bb(%rip),%rbp        # 60075c <__init_array_end>
  4005a1:       4c 8d 25 b4 01 20 00    lea    0x2001b4(%rip),%r12        # 60075c <__init_array_end>
  4005a8:       4c 89 6c 24 e8          mov    %r13,-0x18(%rsp)
  4005ad:       4c 89 74 24 f0          mov    %r14,-0x10(%rsp)
  4005b2:       4c 89 7c 24 f8          mov    %r15,-0x8(%rsp)
  4005b7:       48 89 5c 24 d0          mov    %rbx,-0x30(%rsp)
  4005bc:       48 83 ec 38             sub    $0x38,%rsp
  4005c0:       4c 29 e5                sub    %r12,%rbp
  4005c3:       41 89 fd                mov    %edi,%r13d
  4005c6:       49 89 f6                mov    %rsi,%r14
  4005c9:       48 c1 fd 03             sar    $0x3,%rbp
  4005cd:       49 89 d7                mov    %rdx,%r15
  4005d0:       e8 f3 fd ff ff          callq  4003c8 <_init>
  4005d5:       48 85 ed                test   %rbp,%rbp
  4005d8:       74 1c                   je     4005f6 <__libc_csu_init+0x66>
  4005da:       31 db                   xor    %ebx,%ebx
  4005dc:       0f 1f 40 00             nopl   0x0(%rax)
  4005e0:       4c 89 fa                mov    %r15,%rdx
  4005e3:       4c 89 f6                mov    %r14,%rsi
  4005e6:       44 89 ef                mov    %r13d,%edi
  4005e9:       41 ff 14 dc             callq  *(%r12,%rbx,8)
  4005ed:       48 83 c3 01             add    $0x1,%rbx
  4005f1:       48 39 eb                cmp    %rbp,%rbx
  4005f4:       72 ea                   jb     4005e0 <__libc_csu_init+0x50>
  4005f6:       48 8b 5c 24 08          mov    0x8(%rsp),%rbx
  4005fb:       48 8b 6c 24 10          mov    0x10(%rsp),%rbp
  400600:       4c 8b 64 24 18          mov    0x18(%rsp),%r12
  400605:       4c 8b 6c 24 20          mov    0x20(%rsp),%r13
  40060a:       4c 8b 74 24 28          mov    0x28(%rsp),%r14
  40060f:       4c 8b 7c 24 30          mov    0x30(%rsp),%r15
  400614:       48 83 c4 38             add    $0x38,%rsp
  400618:       c3                      retq   
  400619:       90                      nop
  40061a:       90                      nop
  40061b:       90                      nop
  40061c:       90                      nop
  40061d:       90                      nop
  40061e:       90                      nop
  40061f:       90                      nop

0000000000400620 <__do_global_ctors_aux>:
  400620:       55                      push   %rbp
  400621:       48 89 e5                mov    %rsp,%rbp
  400624:       53                      push   %rbx
  400625:       48 83 ec 08             sub    $0x8,%rsp
  400629:       48 8b 05 30 01 20 00    mov    0x200130(%rip),%rax        # 600760 <__CTOR_LIST__>
  400630:       48 83 f8 ff             cmp    $0xffffffffffffffff,%rax
  400634:       74 19                   je     40064f <__do_global_ctors_aux+0x2f>
  400636:       bb 60 07 60 00          mov    $0x600760,%ebx
  40063b:       0f 1f 44 00 00          nopl   0x0(%rax,%rax,1)
  400640:       48 83 eb 08             sub    $0x8,%rbx
  400644:       ff d0                   callq  *%rax
  400646:       48 8b 03                mov    (%rbx),%rax
  400649:       48 83 f8 ff             cmp    $0xffffffffffffffff,%rax
  40064d:       75 f1                   jne    400640 <__do_global_ctors_aux+0x20>
  40064f:       48 83 c4 08             add    $0x8,%rsp
  400653:       5b                      pop    %rbx
  400654:       c9                      leaveq 
  400655:       c3                      retq   
  400656:       90                      nop
  400657:       90                      nop

下面使用objdump反汇编查看.data的内容: 
$objdump -d -j .data test 
.data区保存的是初始化的全局变量。

test:     file format elf64-x86-64


Disassembly of section .data:

0000000000600950 <__data_start>:
  600950:       00 00                   add    %al,(%rax)
        ...

0000000000600954 <global_data>:
  600954:       04 00 00 00  

下面使用objdump反汇编查看.bss的内容: 
$objdump -d -j .bss test 
.bss区保存的是未初始化的全局变量,linux会默认将未初始化的变量置为0。

test:     file format elf64-x86-64


Disassembly of section .bss:

0000000000600958 <completed.6347>:
        ...

0000000000600960 <dtor_idx.6349>:
        ...

0000000000600968 <global_data_2>:

下面命令可以看到test文件中所有的符号: 
$readelf -s test 
Value的值是符号的地址。

Symbol table '.dynsym' contains 5 entries:
   Num:    Value          Size Type    Bind   Vis      Ndx Name
     0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND 
     1: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND printf@GLIBC_2.2.5 (2)
     2: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND __gmon_start__
     3: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND puts@GLIBC_2.2.5 (2)
     4: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND __libc_start_main@GLIBC_2.2.5 (2)

Symbol table '.symtab' contains 67 entries:
   Num:    Value          Size Type    Bind   Vis      Ndx Name
     0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND 
     1: 0000000000400200     0 SECTION LOCAL  DEFAULT    1 
     2: 000000000040021c     0 SECTION LOCAL  DEFAULT    2 
     3: 000000000040023c     0 SECTION LOCAL  DEFAULT    3 
     4: 0000000000400260     0 SECTION LOCAL  DEFAULT    4 
     5: 0000000000400280     0 SECTION LOCAL  DEFAULT    5 
     6: 00000000004002f8     0 SECTION LOCAL  DEFAULT    6 
     7: 000000000040033c     0 SECTION LOCAL  DEFAULT    7 
     8: 0000000000400348     0 SECTION LOCAL  DEFAULT    8 
     9: 0000000000400368     0 SECTION LOCAL  DEFAULT    9 
    10: 0000000000400380     0 SECTION LOCAL  DEFAULT   10 
    11: 00000000004003c8     0 SECTION LOCAL  DEFAULT   11 
    12: 00000000004003e0     0 SECTION LOCAL  DEFAULT   12 
    13: 0000000000400420     0 SECTION LOCAL  DEFAULT   13 
    14: 0000000000400658     0 SECTION LOCAL  DEFAULT   14 
    15: 0000000000400668     0 SECTION LOCAL  DEFAULT   15 
    16: 00000000004006bc     0 SECTION LOCAL  DEFAULT   16 
    17: 00000000004006e0     0 SECTION LOCAL  DEFAULT   17 
    18: 0000000000600760     0 SECTION LOCAL  DEFAULT   18 
    19: 0000000000600770     0 SECTION LOCAL  DEFAULT   19 
    20: 0000000000600780     0 SECTION LOCAL  DEFAULT   20 
    21: 0000000000600788     0 SECTION LOCAL  DEFAULT   21 
    22: 0000000000600918     0 SECTION LOCAL  DEFAULT   22 
    23: 0000000000600920     0 SECTION LOCAL  DEFAULT   23 
    24: 0000000000600950     0 SECTION LOCAL  DEFAULT   24 
    25: 0000000000600958     0 SECTION LOCAL  DEFAULT   25 
    26: 0000000000000000     0 SECTION LOCAL  DEFAULT   26 
    27: 000000000040044c     0 FUNC    LOCAL  DEFAULT   13 call_gmon_start
    28: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS crtstuff.c
    29: 0000000000600760     0 OBJECT  LOCAL  DEFAULT   18 __CTOR_LIST__
    30: 0000000000600770     0 OBJECT  LOCAL  DEFAULT   19 __DTOR_LIST__
    31: 0000000000600780     0 OBJECT  LOCAL  DEFAULT   20 __JCR_LIST__
    32: 0000000000400470     0 FUNC    LOCAL  DEFAULT   13 __do_global_dtors_aux
    33: 0000000000600958     1 OBJECT  LOCAL  DEFAULT   25 completed.6347
    34: 0000000000600960     8 OBJECT  LOCAL  DEFAULT   25 dtor_idx.6349
    35: 00000000004004e0     0 FUNC    LOCAL  DEFAULT   13 frame_dummy
    36: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS crtstuff.c
    37: 0000000000600768     0 OBJECT  LOCAL  DEFAULT   18 __CTOR_END__
    38: 0000000000400758     0 OBJECT  LOCAL  DEFAULT   17 __FRAME_END__
    39: 0000000000600780     0 OBJECT  LOCAL  DEFAULT   20 __JCR_END__
    40: 0000000000400620     0 FUNC    LOCAL  DEFAULT   13 __do_global_ctors_aux
    41: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS test.c
    42: 0000000000600920     0 OBJECT  LOCAL  DEFAULT   23 _GLOBAL_OFFSET_TABLE_
    43: 000000000060075c     0 NOTYPE  LOCAL  DEFAULT   18 __init_array_end
    44: 000000000060075c     0 NOTYPE  LOCAL  DEFAULT   18 __init_array_start
    45: 0000000000600788     0 OBJECT  LOCAL  DEFAULT   21 _DYNAMIC
    46: 0000000000600950     0 NOTYPE  WEAK   DEFAULT   24 data_start
    47: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND printf@@GLIBC_2.2.5
    48: 0000000000400580     2 FUNC    GLOBAL DEFAULT   13 __libc_csu_fini
    49: 0000000000400420     0 FUNC    GLOBAL DEFAULT   13 _start
    50: 0000000000600968     4 OBJECT  GLOBAL DEFAULT   25 global_data_2
    51: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND __gmon_start__
    52: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND _Jv_RegisterClasses
    53: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND puts@@GLIBC_2.2.5
    54: 0000000000400658     0 FUNC    GLOBAL DEFAULT   14 _fini
    55: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND __libc_start_main@@GLIBC_
    56: 0000000000400668     4 OBJECT  GLOBAL DEFAULT   15 _IO_stdin_used
    57: 0000000000600950     0 NOTYPE  GLOBAL DEFAULT   24 __data_start
    58: 0000000000400670     0 OBJECT  GLOBAL HIDDEN    15 __dso_handle
    59: 0000000000600778     0 OBJECT  GLOBAL HIDDEN    19 __DTOR_END__
    60: 0000000000400590   137 FUNC    GLOBAL DEFAULT   13 __libc_csu_init
    61: 0000000000600958     0 NOTYPE  GLOBAL DEFAULT  ABS __bss_start
    62: 0000000000600970     0 NOTYPE  GLOBAL DEFAULT  ABS _end
    63: 0000000000600958     0 NOTYPE  GLOBAL DEFAULT  ABS _edata
    64: 0000000000600954     4 OBJECT  GLOBAL DEFAULT   24 global_data
    65: 0000000000400504   114 FUNC    GLOBAL DEFAULT   13 main
    66: 00000000004003c8     0 FUNC    GLOBAL DEFAULT   11 _init

下面命令来查看文件的段信息: 
$readelf -l test 
区到段的映射,基本上是按照区的顺序进行映射。 
如果Flags为R和E,表示该段可读和可执行。 
如果Flags为W,表示该段可写。 
VirtAddr是每个段的虚拟起始地址。这个地址并不是位于真正内存上的地址(物理地址)。

Elf file type is EXEC (Executable file)
Entry point 0x400420
There are 8 program headers, starting at offset 64

Program Headers:
  Type           Offset             VirtAddr           PhysAddr
                 FileSiz            MemSiz              Flags  Align
  PHDR           0x0000000000000040 0x0000000000400040 0x0000000000400040
                 0x00000000000001c0 0x00000000000001c0  R E    8
  INTERP         0x0000000000000200 0x0000000000400200 0x0000000000400200
                 0x000000000000001c 0x000000000000001c  R      1
      [Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]
  LOAD           0x0000000000000000 0x0000000000400000 0x0000000000400000
                 0x000000000000075c 0x000000000000075c  R E    200000
  LOAD           0x0000000000000760 0x0000000000600760 0x0000000000600760
                 0x00000000000001f8 0x0000000000000210  RW     200000
  DYNAMIC        0x0000000000000788 0x0000000000600788 0x0000000000600788
                 0x0000000000000190 0x0000000000000190  RW     8
  NOTE           0x000000000000021c 0x000000000040021c 0x000000000040021c
                 0x0000000000000044 0x0000000000000044  R      4
  GNU_EH_FRAME   0x00000000000006bc 0x00000000004006bc 0x00000000004006bc
                 0x0000000000000024 0x0000000000000024  R      4
  GNU_STACK      0x0000000000000000 0x0000000000000000 0x0000000000000000
                 0x0000000000000000 0x0000000000000000  RW     8

 Section to Segment mapping:
  Segment Sections...
   00     
   01     .interp 
   02     .interp .note.ABI-tag .note.gnu.build-id .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rela.dyn .rela.plt .init .plt .text .fini .rodata .eh_frame_hdr .eh_frame 
   03     .ctors .dtors .jcr .dynamic .got .got.plt .data .bss 
   04     .dynamic 
   05     .note.ABI-tag .note.gnu.build-id 
   06     .eh_frame_hdr 
   07 

如上所示,段有多种类型,下面介绍LOAD类型 
LOAD:该段的内容从可执行文件中获取。Offset标识内核从文件读取的位置。FileSiz标识读取多少字节。

那么,执行test之后的进程的段布局是如何呢? 
可以通过cat /proc/pid/maps来查看。pid是进程的pid。 
但是该test运行时间很短,可以使用gdb加断点来运行,或者在return语句之前加上sleep()。

下面使用gdb加断点的形式:

GNU gdb (GDB) Red Hat Enterprise Linux (7.2-50.el6)
Copyright (C) 2010 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-redhat-linux-gnu".
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>...
Reading symbols from /data/readyao/qqlive_zb_prj/server/cgi_push_post_replay/lib/test...(no debugging symbols found)...done.
(gdb) b main
Breakpoint 1 at 0x400508
(gdb) r
Starting program: /data/readyao/qqlive_zb_prj/server/cgi_push_post_replay/lib/test 
[Thread debugging using libthread_db enabled]

Breakpoint 1, 0x0000000000400508 in main ()
Missing separate debuginfos, use: debuginfo-install glibc-2.12-1.49.tl1.x86_64

$cat /proc/6929/maps 

00400000-00401000 r-xp 00000000 ca:11 8626925                            /test
00600000-00601000 rw-p 00000000 ca:11 8626925                            /test
7ffff762d000-7ffff7644000 r-xp 00000000 ca:01 332328                     /lib64/libpthread-2.12.so
7ffff7644000-7ffff7843000 ---p 00017000 ca:01 332328                     /lib64/libpthread-2.12.so
7ffff7843000-7ffff7844000 r--p 00016000 ca:01 332328                     /lib64/libpthread-2.12.so
7ffff7844000-7ffff7845000 rw-p 00017000 ca:01 332328                     /lib64/libpthread-2.12.so
7ffff7845000-7ffff7849000 rw-p 00000000 00:00 0 
7ffff7849000-7ffff784b000 r-xp 00000000 ca:01 332237                     /lib64/libdl-2.12.so
7ffff784b000-7ffff7a4b000 ---p 00002000 ca:01 332237                     /lib64/libdl-2.12.so
7ffff7a4b000-7ffff7a4c000 r--p 00002000 ca:01 332237                     /lib64/libdl-2.12.so
7ffff7a4c000-7ffff7a4d000 rw-p 00003000 ca:01 332237                     /lib64/libdl-2.12.so
7ffff7a4d000-7ffff7bd3000 r-xp 00000000 ca:01 332102                     /lib64/libc-2.12.so
7ffff7bd3000-7ffff7dd3000 ---p 00186000 ca:01 332102                     /lib64/libc-2.12.so
7ffff7dd3000-7ffff7dd7000 r--p 00186000 ca:01 332102                     /lib64/libc-2.12.so
7ffff7dd7000-7ffff7dd8000 rw-p 0018a000 ca:01 332102                     /lib64/libc-2.12.so
7ffff7dd8000-7ffff7ddd000 rw-p 00000000 00:00 0 
7ffff7ddd000-7ffff7dfd000 r-xp 00000000 ca:01 332126                     /lib64/ld-2.12.so
7ffff7ed9000-7ffff7edc000 rw-p 00000000 00:00 0 
7ffff7eeb000-7ffff7eee000 r-xp 00000000 ca:01 336319                     /lib64/libonion_security.so.1.0.13
7ffff7eee000-7ffff7fee000 ---p 00003000 ca:01 336319                     /lib64/libonion_security.so.1.0.13
7ffff7fee000-7ffff7fef000 rw-p 00003000 ca:01 336319                     /lib64/libonion_security.so.1.0.13
7ffff7fef000-7ffff7ffb000 rw-p 00000000 00:00 0 
7ffff7ffb000-7ffff7ffc000 r-xp 00000000 00:00 0                          [vdso]
7ffff7ffc000-7ffff7ffd000 r--p 0001f000 ca:01 332126                     /lib64/ld-2.12.so
7ffff7ffd000-7ffff7ffe000 rw-p 00020000 ca:01 332126                     /lib64/ld-2.12.so
7ffff7ffe000-7ffff7fff000 rw-p 00000000 00:00 0 
7ffffffea000-7ffffffff000 rw-p 00000000 00:00 0                          [stack]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0                  [vsyscall]

前面一部分是VMA的起始地址和结束地址。 
最后一部分是该区域内容所属文件。 
在32位系统中,进程地址空间为4G,分为用户空间和内核空间。 


从下面可以看到栈的地址是向下生长,堆的地址是向上生长。 

通过反汇编定位段错误:

段错误是程序员最讨厌的问题之一,其发生往往很突然,且破坏巨大。典型的段错误是由于操作内存不当引起的(如使用野指针或访问受保护的地址等),发生段错误时,内核以一个信号SIGSEGV强行终止进程,留下的出错信息极少,从而导致难以定位。但利用gdb和反汇编工具,可以较准确地定位段错误产生的原因。但想用这种方法调试,一些准备工作和工具是必需的。

准备工作:

(1)coredump:进程异常中止时,内核生成的记录文件,其中保存了进程异常时所占用的内存和CPU资源,如pc计数器、各个寄存器的值等。这个文件是调试段错误最重要的依据。要使内核生成coredump,需要在内核配置中打开CONFIG_ELF_CORE选项,如果没有打开,将其选上后重新编译内核即可。

此外,利用命令ulimit -c unlimited,可以设置coredump大小为不受限制,可以保存更完整的信息。文件/proc/sys/kernel/core_pattern可以配置生成coredump的命名格式,如果不设置格式,则coredump默认生成的位置在出错进程的目录下,且生成的core同名,也就意味着旧的coredump可能被新的coredump所覆盖。如果我想在/tmp目录下生成以core.pid格式命名的coredump文件,只需执行命令:

echo "/tmp/core.%p" > /proc/sys/kernel/core_pattern

(2)编译:为了利用gdb进行调试,在编译程序时,需要在编译选项中加入-g 选项,以将调试信息编译到目标文件中。

(3)反汇编:顾名思义,反汇编就是将编译好的二进制可执行文件翻译成汇编文件。一般来说,编译器会自带一套反汇编工具,只有选择正确的工具才能正确地进行反汇编,这不难理解。比如我是用gcc4.6.3编译的用于mips的应用程序,那么,在编译器的目录下可以找到gcc463/usr/bin/mipsel-buildroot-linux-uclibc-objdump,这就是我要使用的反汇编工具。将二进制文件反汇编成汇编文件只需执行命令:XXXX-objdump -S XXXX(程序名),即可生成可以阅读的、关联到C代码的汇编代码,如下所示:

    status = httpRpmPost(reqId);    
  42f208:    0320f809     jalr    t9
  42f20c:    00808021     move    s0,a0
  42f210:    8fbc0018     lw    gp,24(sp)
    if (RPM_OK != status && RPM_DONE != status)
  42f214:    10400009     beqz    v0,42f23c <postDataApStatusJson.part.4+0x68>
  42f218:    3c120061     lui    s2,0x61
  42f21c:    24030002     li    v1,2
  42f220:    10430006     beq    v0,v1,42f23c <postDataApStatusJson.part.4+0x68>
  42f224:    3c040061     lui    a0,0x61
    {
        RPM_AP_ERROR("httpRpmPost error!");
  42f228:    2484c928     addiu    a0,a0,-14040
  42f22c:    2645cc98     addiu    a1,s2,-13160
  42f230:    8f999e50     lw    t9,-25008(gp)
  42f234:    0810bca3     j    42f28c <postDataApStatusJson.part.4+0xb8>
  42f238:    24060327     li    a2,807
        return RPM_ERROR;
    }      
    
    httpStatusSet(reqId, HTTP_OK);
  42f23c:    8f998088     lw    t9,-32632(gp)
  42f240:    02002021     move    a0,s0
  42f244:    0320f809     jalr    t9
  42f248:    00002821     move    a1,zero
  42f24c:    8fbc0018     lw    gp,24(sp)

可以看到,C代码下面跟着一串汇编代码,而汇编代码前面有一段地址,这个地址是什么呢?如果熟悉Linux进程空间的概念,很容易就可以联想到,这个地址其实就是相应的汇编指令在.text段(即代码段)中的地址。也就是说,这个地址就是我们用于定位具体出错地点的依据。(4)gdb:可以说是Linux下调试程序最常用的工具,功能强大,操作也很简单。对于mips程序调试,只需安装相应的gdb:mips-linux-gdb即可。


开始调试:

上面的准备工作都完成后,就可以开始调试了。当进程再次异常终止时,就可以在/tmp目录下找到coredump文件:比如core.126(进程id为126的进程生成的coredump)。
用gdb的-c选项打开coredump:mips-linux-gdb -c /tmp/core.126,可以看到如下信息:

<span style="font-size:14px;">GNU gdb (GDB) 7.4.1
Copyright (C) 2012 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "--host=i686-pc-linux-gnu --target=mips-linux".
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Core was generated by `/usr/bin/httpd'.
Program terminated with signal 11, Segmentation fault.
#0  0x2b17ff50 in ?? ()
(gdb) </span>

前面是gdb的版本信息,不必理会。我们主要关注下面的内容:
<span style="font-size:14px;">Core was generated by `/usr/bin/httpd'.
Program terminated with signal 11, Segmentation fault.
#0  0x2b17ff50 in ?? ()</span>
表示这个coredump是为进程httpd生成的,而进程退出的原因是signal 11,即SIGSEGV,这正是我们想要的。最后一行,0x2b17ff50是一个地址,这里??的地方本来应该显示一个函数名,之所以这里没有显示,我猜想这应该是一个库函数,而编译这个库时,并没有带入-g信息。
不要紧,接下来只需要输入where,即可显示信号产生时程序中止的位置:

<span style="font-size:14px;">Core was generated by `/usr/bin/httpd'.
Program terminated with signal 11, Segmentation fault.
#0  0x2b17ff50 in ?? ()
(gdb) where
#0  0x2b17ff50 in ?? ()
#1  0x0045c034 in ?? ()
Backtrace stopped: previous frame identical to this frame (corrupt stack?)
(gdb) </span>

    至此,我们已经拿到最重要的信息:0x0045c034,就是进程中止时停留的位置。对照上面生成的反汇编文件,搜索45c034,即可找到:

    out = cJSON_Print(root);
  45c00c:    8f9981ec     lw    t9,-32276(gp)
  45c010:    00000000     nop
  45c014:    0320f809     jalr    t9
  45c018:    02402021     move    a0,s2
  45c01c:    8fbc0010     lw    gp,16(sp)
    httpnPrintf(reqId, strlen(out) + 1, "%s\n", out);
  45c020:    00402021     move    a0,v0
  45c024:    8f999fe4     lw    t9,-24604(gp)
  45c028:    00000000     nop
  45c02c:    0320f809     jalr    t9
  45c030:    00408021     move    s0,v0
  45c034:    8fbc0010     lw    gp,16(sp)
  45c038:    8fa47e48     lw    a0,32328(sp)
  45c03c:    3c060062     lui    a2,0x62
  45c040:    8f9981f0     lw    t9,-32272(gp)
  45c044:    24450001     addiu    a1,v0,1
  45c048:    24c6cdb8     addiu    a2,a2,-12872
  45c04c:    0320f809     jalr    t9
  45c050:    02003821     move    a3,s0
  45c054:    8fbc0010     lw    gp,16(sp)
  45c058:    00000000     nop
    RPM_MONITORAP_TRACE("\r\n%s\r\n\r\n", out);

可以看到,出错时,对应的C函数是httpnPrintf,对应的汇编代码为:    lw    gp, 16(sp)。

在反汇编文件中再稍微对照上下文,即可知道具体是哪个模块、哪个文件中的调用。如果看得懂汇编代码,基本可以定位到函数中的具体语句,即使看不懂汇编,利用打印调试或者静态代码分析等常规调试手段也基本可以定位到具体的出错原因了。在本例中,最终确定这个函数出错的原因是操作了调用malloc(0)而获取的一个空指针(malloc(0)返回什么),着实令人始料未及。
 

不能使用GDB的地方,也可以使用反汇编来定位段错误的位置。一般在发生段错误的时候,都会有些信息打印出来,如下:

Oops: Data Abort caused by READ instruction! 
Fault: Alignment fault 
pc: 0021034c 
r0: 20000053 r1: 00000001 r2: 00000000 r3: 20000053 
r4: aaaaaaaa r5: 00256984 r6: aaaaaaaa r7: dddddddd 
r8: aaaaaaaa r9: dddddddd r10: 00060000 
fp: 00000000 ip: 00000000 sp: 00240bc0 
SPSR: 600000d3

    从上面看出,PC就是程序计数器,即程序运行到此位置发生错误,然后我们利用objdump将运行的程序反汇编,跟踪查询到问题出现的位置。
版权说明:文章内容转载自下面文章

   《ELF文件格式分析》转载自:https://www.cnblogs.com/cdcode/p/5551649.html
   《Linux驱动mmap内存映射》转载自https://www.cnblogs.com/wanghuaijun/p/7624564.html
   《通过反汇编定位段错误》转载自https://blog.csdn.net/ringrang/article/details/60596846
   《Linux C 内存管理》转载自: https://blog.csdn.net/zqixiao_09/article/details/50381476 
   《readelf命令和ELF文件详解》转载自:https://blog.csdn.net/linux_ever/article/details/78210089 

猜你喜欢

转载自blog.csdn.net/li_wen01/article/details/88400678
今日推荐