<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>ftofficer&#124;张聪的blog &#187; linux</title>
	<atom:link href="http://blog.ftofficer.com/tag/linux/feed/" rel="self" type="application/rss+xml" />
	<link>http://blog.ftofficer.com</link>
	<description>A Newbie on the Way</description>
	<lastBuildDate>Sun, 12 Dec 2010 14:35:49 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.3.1</generator>
<cloud domain='blog.ftofficer.com' port='80' path='/?rsscloud=notify' registerProcedure='' protocol='http-post' />
		<item>
		<title>计算机不存在灵异事件</title>
		<link>http://blog.ftofficer.com/2009/08/there-is-no-magic-in-computer/?utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=rss</link>
		<comments>http://blog.ftofficer.com/2009/08/there-is-no-magic-in-computer/#comments</comments>
		<pubDate>Fri, 31 Jul 2009 17:16:55 +0000</pubDate>
		<dc:creator>Zhang Cong</dc:creator>
				<category><![CDATA[技术]]></category>
		<category><![CDATA[debugging]]></category>
		<category><![CDATA[elf]]></category>
		<category><![CDATA[got]]></category>
		<category><![CDATA[linux]]></category>

		<guid isPermaLink="false">http://blog.ftofficer.com/?p=10009</guid>
		<description><![CDATA[再把这条铁律拉出来一次。 今天碰到了一个bug，服务器在运行时会core dump在一个很灵异的地方，排除这个错误的过程，以及最后发现的错误结果很具有典型性，所牵涉到的技术也很多，拿来作为Linux调试的课程挺好的。:-P 整个里面假设读者已经知道怎么用gdb，如果不知道，请参见GDB Manual 首先，很幸运的是，这个问题是可以很容易重现的，而且更重要的，有core dump。 拿到core dump之后，惯例是查看一下调用栈：（为了避免泄漏商业秘密，所有函数名，文件名什么的都用foo啊，bar啊，foobar啊，blabla啊等等代替）。 (gdb) bt #0 0x000000eb in ?? () #1 0x3aa1d941 in ?? () #2 0x000001f8 in ?? () #3 0x080cf888 in foo (range=10000) at foo/foo.c:18 #4 0x080c1f29 in bar () at bar/bar.c:423 [....] (gdb) info f 0 Stack frame at 0xbfc42548: eip = 0xeb; saved eip 0x3aa1d941 [...]]]></description>
			<content:encoded><![CDATA[<p>再把这条铁律拉出来一次。</p>
<p>今天碰到了一个bug，服务器在运行时会core dump在一个很灵异的地方，排除这个错误的过程，以及最后发现的错误结果很具有典型性，所牵涉到的技术也很多，拿来作为Linux调试的课程挺好的。:-P</p>
<p>整个里面假设读者已经知道怎么用gdb，如果不知道，请参见<a href="http://sourceware.org/gdb/current/onlinedocs/gdb_toc.html">GDB Manual</a></p>
<p>首先，很幸运的是，这个问题是可以很容易重现的，而且更重要的，有core dump。<br />
拿到core dump之后，惯例是查看一下调用栈：（为了避免泄漏商业秘密，所有函数名，文件名什么的都用foo啊，bar啊，foobar啊，blabla啊等等代替）。</p>
<p><code>(gdb) bt<br />
#0  0x000000eb in ?? ()<br />
#1  0x3aa1d941 in ?? ()<br />
#2  0x000001f8 in ?? ()<br />
#3  0x080cf888 in foo (range=10000) at foo/foo.c:18<br />
#4  0x080c1f29 in bar () at bar/bar.c:423<br />
[....]</code></p>
<p><code>(gdb) info f 0<br />
Stack frame at 0xbfc42548:<br />
<strong>eip = 0xeb</strong>; saved eip 0x3aa1d941<br />
called by frame at 0xbfc4254c<br />
Arglist at 0xbfc42540, args:<br />
Locals at 0xbfc42540, Previous frame's sp is 0xbfc42548<br />
Saved registers:<br />
eip at 0xbfc42544</code></p>
<p><code>(gdb) f 3<br />
#3  0x080cf888 in foo (range=10000) at foo/foo.c:18<br />
18      return ((u32)random()) % range;</code></p>
<p>相当的灵异，栈上的0,1,2都是，一个返回地址怎么可能是0x1f8，而且，core dump的原因是因为eip跑飞到了0xeb。到frame 3的时候看起来正常了，但是出错的地方在random这种简单的库函数上。不过既然frame 3往下的部分都是好的，我们有理由认为栈并没有被搞坏。因为GDB在显示调用栈的时候可能会把一些不正确的调用栈也显示出来，我们干脆直接看内存：</p>
<p><code>(gdb) x/10w $esp<br />
0xbfc42544:     0x3aa1d941      0x000001f8      <strong>0x080cf888</strong> 0x09582930<br />
0xbfc42554:     0x0833f038      0xbfc42678      0x080c1f29      0x00002710<br />
0xbfc42564:     0x0823747f      0xb7ca168c<br />
</code></p>
<p>看加粗的部分：这里就是frame 3返回地址，而上面的东西，就是bt显示出来的frame 1和frame 2了，而frame 0就是当前的eip了：它跑飞到了0xeb。</p>
<p>GDB果然在显示栈的时候做了手脚。</p>
<p>OK，我们反汇编看看这个返回地址，到底干了什么：</p>
<p><code>(gdb) disassemble 0x080cf888<br />
Dump of assembler code for function _Z6foom:<br />
0x080cf85c &lt;_Z6foom+0&gt;:      push   %ebp<br />
0x080cf85d &lt;_Z6foom+1&gt;:      mov    %esp,%ebp<br />
0x080cf85f &lt;_Z6foom+3&gt;:      push   %ebx<br />
0x080cf860 &lt;_Z6foom+4&gt;:      sub    $0x4,%esp<br />
[…]<br />
0x080cf87a &lt;_Z6foom+30&gt;:     movl   $0x0,0xfffffff8(%ebp)<br />
0x080cf881 &lt;_Z6foom+37&gt;:     jmp    0x80cf893 &lt;_Z6foom+55&gt;<br />
<strong>0x080cf883 &lt;_Z6foom+39&gt;:     call   0x8051f90</strong><br />
0x080cf888 &lt;_Z6foom+44&gt;:     mov    $0x0,%edx<br />
[…]<br />
0x080cf899 &lt;_Z6foom+61&gt;:     pop    %ebx<br />
0x080cf89a &lt;_Z6foom+62&gt;:     pop    %ebp<br />
0x080cf89b &lt;_Z6foom+63&gt;:     ret<br />
End of assembler dump.<br />
</code></p>
<p>看起来也没干啥，继续看调用的地址是啥吧：</p>
<p><code>(gdb) disassemble 0x8051f90<br />
Dump of assembler code for function <strong>random@plt</strong>:<br />
0x08051f90 :      jmp    *0x833f140<br />
0x08051f96 :      push   $0x1f8<br />
0x08051f9b :     jmp    0x8051b90 &lt;_init+24&gt;</code></p>
<p>有点意思啊，random@plt，PLT是什么？PLT是Linux ELF格式可执行文件当中的一个部分，称为Procedure Linkage Table。PLT是程序为了实现Linux共享库的动态迟绑定(Lazy Binding)而引入的一种机制。关于 ELF格式、PLT，和下面要提到的GOT（Global Offset Table）的资料，可以参见下面这些链接：<br />
<a href="http://www.linuxjournal.com/article/1059">The ELF Object File Format: Introduction</a><br />
<a href="http://www.linuxjournal.com/article/1060">http://www.linuxjournal.com/article/1060</a><br />
<a href="http://www.csie.nctu.edu.tw/~shieyuan/course/spb/lectures/sp13.ppt">UNIX ELF File Format (PPT)</a></p>
<p>好了，言归正传，就算你不知道PLT，你也可以继续往下看。最土的办法，既然jump到一个地方，就看看这个地方是什么吧。</p>
<p><code>(gdb) p/x *0x833f140<br />
$6 = <strong>0x8051f96</strong></code></p>
<p>显然这个重新跳回到了PLT当中，看下面：</p>
<p><code>(gdb) disassemble 0x8051f96<br />
Dump of assembler code for function random@plt:<br />
0x08051f90 :      jmp    *0x833f140<br />
0x08051f96 :      push   $0x1f8                                       // 这是栈上的 0x1f8<br />
0x08051f9b :     jmp    0x8051b90 &lt;_init+24&gt;<br />
</code></p>
<p>又一个jmp，这次jmp到了什么地方呢？</p>
<p><code>(gdb) disassemble 0x8051b90<br />
No function contains specified address.</code></p>
<p>这是怎么回事？因为没有对应的符号，或者这一段代码不在.text里面。不过我们还可以用其他的方法看到指令：</p>
<p><code>(gdb) x/10i 0x8051b90<br />
0x8051b90 &lt;_init+24&gt;: pushl  0x833f03c                     // 这是栈上的另一个东西的来源<br />
0x8051b96 &lt;_init+30&gt;: jmp    *0x833f040<br />
0x8051b9c &lt;_init+36&gt;: add    %al,(%eax)<br />
0x8051b9e &lt;_init+38&gt;: add    %al,(%eax)</code></p>
<p>又有一个jmp，继续。</p>
<p><code>(gdb) p/x *0x833f040<br />
$7 = 0xeb</code></p>
<p>怎么会这样？这个指针被覆写了，Jump到了0xeb，然后就core了。</p>
<p>看08833f040这个地址，属于GOT（Global Offset Table）:</p>
<p><code>(gdb) x/10w 0x833f040<br />
0x833f040 &lt;_GLOBAL_OFFSET_TABLE_+8&gt;:    <strong>0x000000eb</strong> 0x00000002      0x00000006      0xb7f4401c<br />
0x833f050 &lt;_GLOBAL_OFFSET_TABLE_+24&gt;:   0x08051bd6      0xb7d8e9b0      0xb7d85260      0x08051c06<br />
0x833f060 &lt;_GLOBAL_OFFSET_TABLE_+40&gt;:   0xb7d32ae0      0xb7da88d0</code></p>
<p>我们从GOT头上开始看：</p>
<p><code>(gdb) x/10w 0x833f040-8<br />
0x833f038 &lt;_GLOBAL_OFFSET_TABLE_&gt;: 0x00000000   0x3aa1d941      <strong>0x000000eb</strong> 0x00000002<br />
0x833f048 &lt;_GLOBAL_OFFSET_TABLE_+16&gt;:   0x00000006      0xb7f4401c      0x08051bd6      0xb7d8e9b0<br />
0x833f058 &lt;_GLOBAL_OFFSET_TABLE_+32&gt;:   0xb7d85260      0x08051c06</code></p>
<p>仍然说Lazy Binding相关的东西，如果你了解ELF的Lazy Binding，你会知道GOT的前面四个字是特殊的，而其中的GOT+8的地方是_dl_runtime_resolve，而上面的最后一个jmp就是跳到这里。但是，这里却变成了0xeb。</p>
<p>错误的原因找到了，但是，究竟是谁干的？我们知道gdb有一个很好的feature叫做watchpoint，这个在VS当中被叫做Data Breakpoint。它们的本质是一样的，都是通过硬件来实现对某个地址的监控，你可以设定当这块内存被读、写、或者被访问（包括读写）的时候触发该断点。很好，我们就在这里加上一个watchpoint。注意watchpoint的语法，你需要在地址前面加一个*。</p>
<p>我们重新启动服务器，然后attach到这个进程，对出现错误的地址 0x0833f040加了watchpoint。为什么可以这么做？因为GOT在内存当中的位置是固定的，我们知道，无论你第几次启动一个程序，GOT都会被加载到同一个地址。</p>
<p><code>(gdb) watch *0x0833f040<br />
Hardware watchpoint 2 added</code></p>
<p>然后继续执行</p>
<p><code>(gdb) c<br />
Continuing.</code></p>
<p>然后，坐到一边喝茶去吧，等待你看到gdb被断进去。<br />
然后，终于，等到了。</p>
<p><code>Hardware watchpoint 2: *137621568</code></p>
<p>Old value = -1208695104<br />
New value = 235<br />
CFoo::foobar (this=0xb1df5810) at foo/foobar.c:477<br />
477                   blablabla_func(0, sth-&gt;data.id, tmpData);<br />
Current language:  auto; currently c++</p>
<p>好了，我们看看这个时候的GOT：</p>
<p><code>(gdb) x/w 0x833f040<br />
0x833f040 &lt;_GLOBAL_OFFSET_TABLE_+8&gt;:    <strong>0x000000eb</strong></code></p>
<p>这个值的的确确被修改了，那么，我们看看栈。</p>
<p><code>(gdb) bt<br />
#0  CFoo::foobar (this=0xb1df5810) at foo/foobar.c:477<br />
#1  0x0812fa69 in CFoo::doSomthing(this=0xb1df5810, action=0xbfb93378, event=0xbfb934d0) at foo/foobar.c:1100<br />
[...]<br />
#10 0x08067a60 in main (argc=0, argv=0x3aa1d95a) at server.c:1289</code></p>
<p>很好，这是我们的代码，过去看看：</p>
<p>对应代码：<br />
<code><strong>LPSOME_DATA_TYPE tmpData;</strong><br />
u64 some_array[MAX_SOME_ARRAY_SIZE];<br />
size_t array_sz = MAX_SOME_ARRAY_SIZE;<br />
[...]<br />
tmpData-&gt;iRet = 0;<br />
tmpData-&gt;iField01 = someVar;<br />
</code><code>tmpData-&gt;iField02 = SOME_CONSTRANT;   // iField02的offset是8，也就是结构的第三个成员。</code><br />
tmpData-&gt;iId = some-&gt;unit_id;<br />
foobar();                  // line 477</p>
<p><code>(gdb) p tmpData<br />
0x833f038</code></p>
<p>OK，因为代码已经被处理过，所以逻辑可能看的不是很清楚，但是很明显，在这里，问题在tmpData，这是一个指针（请参见类型，但是变量名竟然没有任何的表明这个是个指针），没有初始化就用了。非常非常的巧合，这个变量所在的地方正好是GOT的地址，然后，在对iField02变量赋值的时候，_dl_runtime_resolve的地址被覆盖掉了。</p>
<p>至此，凶手已经找到了，但是这个给我们什么启示呢？</p>
<p>计算机没有灵异事件。</p>
<p><strong>你也许会喜欢：</strong>
<ul class="similar-posts">
<li><a href="http://blog.ftofficer.com/2010/04/n-forms-of-call-instructions/" rel="bookmark" title="2010年04月4日">CALL指令有多少种写法</a></li>
<li><a href="http://blog.ftofficer.com/about/" rel="bookmark" title="2009年07月16日">关于我</a></li>
<li><a href="http://blog.ftofficer.com/2007/07/effective-debug-logging-part-3/" rel="bookmark" title="2007年07月23日">Effective Debug Logging (part 3)</a></li>
<li><a href="http://blog.ftofficer.com/2008/01/web%e7%bb%88%e7%ab%af%e6%9c%ba%e5%99%a8/" rel="bookmark" title="2008年01月19日">Web终端机器?</a></li>
<li><a href="http://blog.ftofficer.com/2009/03/%e8%a7%86%e9%a2%91%e7%ac%ac%e5%85%ad%e6%84%9f%e2%80%94%e2%80%94%e5%8f%af%e7%a9%bf%e6%88%b4%e5%bc%8f%e6%89%8b%e5%8a%bf%e6%8e%a7%e5%88%b6%e4%ba%ba%e6%9c%ba%e7%95%8c%e9%9d%a2/" rel="bookmark" title="2009年03月14日">[视频]第六感——可穿戴式手势控制人机界面</a></li>
</ul>
<p><!-- Similar Posts took 14.877 ms --></p>
]]></content:encoded>
			<wfw:commentRss>http://blog.ftofficer.com/2009/08/there-is-no-magic-in-computer/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
	</channel>
</rss>

