本文记录解答MIT 6.828 Lab 1 Exercise 10时遇到的一个Bug。

问题描述

在i386_init入口处设置断点并运行,发现执行memset(edata, 0, end - edata);时,QEMU窗口会打印以下日志并卡住,GDB窗口会异常结束。这是什么原因?

代码如下所示:

void i386_init(void)
{
extern char edata[], end[]; // Before doing anything else, complete the ELF loading process.
// Clear the uninitialized global data (BSS) section of our program.
// This ensures that all static/global variables start out zero.
memset(edata, 0, end - edata); // Initialize the console.
// Can't call cprintf until after we do this!
cons_init(); cprintf("6828 decimal is %o octal!\n", 6828); // Test the stack backtrace function (lab 1 only)
test_backtrace(5); // Drop into the kernel monitor.
while (1)
monitor(NULL);
}

QEMU窗口打印的错误日志:

EAX=00000000 EBX=00000000 ECX=000001a9 EDX=00000000
ESI=00000000 EDI=f0113000 EBP=f010ffd8 ESP=f010ffcc
EIP=f010171b EFL=00000002 [-------] CPL=0 II=0 A20=1 SMM=0 HLT=0
ES =0010 00000000 ffffffff 00cf9300 DPL=0 DS [-WA]
CS =0008 00000000 ffffffff 00cf9a00 DPL=0 CS32 [-R-]
SS =0010 00000000 ffffffff 00cf9300 DPL=0 DS [-WA]
DS =0010 00000000 ffffffff 00cf9300 DPL=0 DS [-WA]
FS =0010 00000000 ffffffff 00cf9300 DPL=0 DS [-WA]
GS =0010 00000000 ffffffff 00cf9300 DPL=0 DS [-WA]
LDT=0000 00000000 0000ffff 00008200 DPL=0 LDT
TR =0000 00000000 0000ffff 00008b00 DPL=0 TSS32-busy
GDT= 00007c4c 00000017
IDT= 00000000 000003ff
CR0=80010011 CR2=00000040 CR3=00112000 CR4=00000000
DR0=00000000 DR1=00000000 DR2=00000000 DR3=00000000
DR6=ffff0ff0 DR7=00000400
EFER=0000000000000000
Triple fault. Halting for inspection via QEMU monitor.

GDB窗口打印的错误日志:

Program received signal SIGTRAP, Trace/breakpoint trap.
The target architecture is assumed to be i386
=> 0xf010171b <memset+73>: Error while running hook_stop:
Cannot access memory at address 0xf010171b
0xf010171b in memset (
v=<error reading variable: Cannot access memory at address 0xf010ffd0>,
c=<error reading variable: Cannot access memory at address 0xf010ffd4>,
n=<error reading variable: Cannot access memory at address 0xf010ffd8>) at lib/string.c:131
1: $ebp = (void *) 0xf010ffd8
2: $esp = (void *) 0xf010ffcc
3: /x $eax = 0x0
4: /x $ebx = 0x0
5: $ecx = 488
6: $edx = 0
8: /x $edi = 0xf0112f04
9: /x $esi = 0x0
10: *0xf0111300@10 = <error: Cannot access memory at address 0xf0111300>
11: *0xf0112f00@10 = <error: Cannot access memory at address 0xf0112f00>
12: *0xf01136a0@10 = <error: Cannot access memory at address 0xf01136a0> asm volatile("cld; rep stosl\n"

定位过程

  1. memset的汇编实现中是重复执行stosl命令,将0依次传到0xf0111300~0xf01136a4这段内存空间,每次传4字节,共需重复2281次。调试中发现,当执行到第2281-488=1793次时,也就是将0传给0xf0112f04这个地址时系统就报错了。

  2. 从官方地址上下载一份干净的代码重新编译执行,发现同样在memset会崩溃,但我记得很早以前第一次下载代码来运行时是正常的,很奇怪。

  3. 注释掉memset这一行,发现可以继续运行,但跑到monitor时会在QEMU窗口不断打印乱码与"unknown command."信息。使用gdb逐步执行时发现是readline时用户根本没输入但依然能读到数据,显示出来是乱码,因此解析输入内容时会报“Unknown command”。

  4. 下午使用gdb跟踪readline及getchar的代码,最终跟踪到通过IN指令来获取输入数据的地方,但只能观察到用户没输入IN指令也能返回,确认不了原因。我怀疑是前面注释了memset语句,导致I/O需要用到的内存空间没初始化,进而出错。因此只能继续定位memset为什么出错。

  5. 晚上决定先确认下是否只有0xf0112f04这个地址的初始化才会有问题,于是memset时避开这个地址,发现果然memset可以成功,但跑到monitor时会崩溃。

	memset(edata, 0, 0xf0112f04 - edata);
memset(0xf0112f08, 0, end - 0xf0112f08);
  1. 后来看代码注释时,发现memset语句的目的是初始化BSS段。
	// Before doing anything else, complete the ELF loading process.
// Clear the uninitialized global data (BSS) section of our program.
// This ensures that all static/global variables start out zero.
memset(edata, 0, end - edata);

通过objdump -h obj/kern/kernel命令查看发现,bss段的地址范围是0xf01130600xf01136a4,而我们要memset的地址范围却是0xf01113000xf0113604!这样除了初始化.bss段之外,还会初始化.got,.got.plt,.data.rel.local和.data.rel.ro.local等4个段。

Sections:
Idx Name Size VMA LMA File off Algn
5 .got 00000008 f0111300 00111300 00012300 2**2
CONTENTS, ALLOC, LOAD, DATA
6 .got.plt 0000000c f0111308 00111308 00012308 2**2
CONTENTS, ALLOC, LOAD, DATA
7 .data.rel.local 00001000 f0112000 00112000 00013000 2**12
CONTENTS, ALLOC, LOAD, DATA
8 .data.rel.ro.local 00000044 f0113000 00113000 00014000 2**2
CONTENTS, ALLOC, LOAD, DATA
9 .bss 00000644 f0113060 00113060 00014044 2**5
ALLOC
  1. 我尝试将memset的地址范围改为bss段的地址范围(0xf0113060~0xf01136a4),结果memset和monitor都正常运行了。先记录至此,以后再回头分析一下。

一个memset导致的血案的更多相关文章

  1. Replication的犄角旮旯(六)-- 一个DDL引发的血案(上)(如何近似估算DDL操作进度)

    <Replication的犄角旮旯>系列导读 Replication的犄角旮旯(一)--变更订阅端表名的应用场景 Replication的犄角旮旯(二)--寻找订阅端丢失的记录 Repli ...

  2. Replication的犄角旮旯(七)-- 一个DDL引发的血案(下)(聊聊logreader的延迟)

    <Replication的犄角旮旯>系列导读 Replication的犄角旮旯(一)--变更订阅端表名的应用场景 Replication的犄角旮旯(二)--寻找订阅端丢失的记录 Repli ...

  3. 一个字母引发的血案 java.io.File中mkdir()和mkdirs()

    一个字母引发的血案 明天开始放年假了,临放假前有个爬虫的任务,其中需要把网络图片保存到本地,很简单,马上写完了代码: //省略部分代码... Long fileId= (Long) data.get( ...

  4. 10-多写一个@Autowired导致程序崩了

    再是javaweb实验六中,是让我们改代码,让它跑起来,结果我少注释了一个,导致一直报错,检查许久没有找到,最后通过代码替换逐步查找,才发现问题.

  5. this.$Message.success('提示信息') 少写了一个c 导致报错

    this.$Message.success('提示信息') 少写了一个c 导致报错 而且 $Message 输出还没显示,导致我以为是没有 $Message 对象了,其实全局对象直接调用即可

  6. 连接池设置导致的“血案” 原创: 一页破书 一页破书 5月6日 这个问题被投诉的几个月了,一直没重视——内部客户嘛😿 问题现象: 隔几周就会出现 A服务调用B服务超时 脚趾头想就是防火墙的问题,A、B两服务之间有防火墙 找运维查看防火墙日志确实断掉了tcp连接,但是是因为B服务5分钟没有回包,下面这个表情就是我当时的心情——其实我们在防火墙、A服务、B服务都抓包了,几十个G的t

    连接池设置导致的“血案” 原创: 一页破书 一页破书 5月6日 这个问题被投诉的几个月了,一直没重视——内部客户嘛

  7. SQL实战——04. 查找所有已经分配部门的员工的last_name和first_name以及dept_no (一个逗号引发的血案)

    查找所有已经分配部门的员工的last_name和first_name以及dept_noCREATE TABLE `dept_emp` (`emp_no` int(11) NOT NULL,`dept_ ...

  8. memset 导致的一个段错误

    原型: void *memset(void *s, int c, size_t n); 解释: memset :是 逐字节 拷贝,即n是指整个变量所占字节,在用于数组时一定要注意n不一定是 数组元素. ...

  9. memset 导致的段错误(segmentation fault)

    在调试Minixml库时,定义了一个结构体: struct ssid_info_s{ std::string wl_ssid_name; std::string wl_ssid_mac; std::s ...

随机推荐

  1. Laravel 5.8: Automatic Policy Resolution

    Laravel 5.8: Automatic Policy Resolution March 26, 2019 One of the new features in Laravel 5.8 allow ...

  2. nc命令用法举

    什么是nc nc是netcat的简写,有着网络界的瑞士军刀美誉.因为它短小精悍.功能实用,被设计为一个简单.可靠的网络工具 nc的作用 (1)实现任意TCP/UDP端口的侦听,nc可以作为server ...

  3. Luogu4191 [CTSC2010]性能优化【多项式,循环卷积】

    题目描述:设$A,B$为$n-1$次多项式,求$A*B^C$在系数模$n+1$,长度为$n$的循环卷积. 数据范围:$n\leq 5*10^5,C\leq 10^9$,且$n$的质因子不超过7,$n+ ...

  4. tomcat发布web项目

    转:https://www.cnblogs.com/skyblue-li/p/7888951.html Tomcat是一种Web服务器,我们自己做好了一个Web项目,就可以通过Tomcat来发布.服务 ...

  5. pwn学习日记Day10 《程序员自我修养》读书笔记

    第一章 从 Hello world 说起 抛出问题: 1.程序为什么要被编译器编译后才能执行? 2.编译器在把C语言程序转换成可以执行的机器码的过程中做了什么,怎么做的? 3.最后编译出来的可执行文件 ...

  6. java跨域配置

    一.问题 使用前后端分离模式开发项目时,往往会遇到这样一个问题 -- 无法跨域获取服务端数据 这是由于浏览器的同源策略导致的,目的是为了安全.在前后端分离开发模式备受青睐的今天,前端和后台项目往往会在 ...

  7. Flutter移动电商实战 --(15)商品推荐区域制作

    1.推荐商品类的编写 这个类接收一个List参数,就是推荐商品的列表,这个列表是可以左右滚动的. /*商品推荐*/ class Recommend extends StatelessWidget { ...

  8. 免费版CloudFlare CDN基本设置参考

    CDN有很多,网上都有介绍,用户比较多的CloudFlare CDN大家都知道,配置起来也比较简单,合理的配置,才能提升网站的速度和网站安全.不同的网站需求配置不一样,以下是我的配置情况,仅供参考. ...

  9. P1944 最长括号匹配_NOI导刊2009提高(1)

    P1944 最长括号匹配_NOI导刊2009提高 题解 宁愿相信世上有鬼,也不能随便相信某谷题目标签 我想了半天然后看了眼题解,发现用栈来模拟就好了 栈来模拟,还要用到一个bool数组,标记是否已经匹 ...

  10. SOCKET原理(转载)

    SOCKET原理 一.套接字(socket)概念 套接字(socket)是通信的基石,是支持TCP/IP协议的网络通信的基本操作单元.它是网络通信过程中端点的抽象表示,包含进行网络通信必须的五种信息: ...