-
Notifications
You must be signed in to change notification settings - Fork 154
/
Copy path1.6.html
1099 lines (576 loc) · 67.3 KB
/
1.6.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<!DOCTYPE HTML>
<html lang="" >
<head>
<meta charset="UTF-8">
<meta content="text/html; charset=utf-8" http-equiv="Content-Type">
<title>监视点 · GitBook</title>
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="description" content="">
<meta name="generator" content="GitBook 3.2.3">
<link rel="stylesheet" href="gitbook/style.css">
<link rel="stylesheet" href="gitbook/gitbook-plugin-intopic-toc/style.css">
<link rel="stylesheet" href="gitbook/gitbook-plugin-page-footer-ex/style/plugin.css">
<link rel="stylesheet" href="gitbook/gitbook-plugin-callouts/plugin.css">
<link rel="stylesheet" href="gitbook/gitbook-plugin-highlight/website.css">
<link rel="stylesheet" href="gitbook/gitbook-plugin-search/search.css">
<link rel="stylesheet" href="gitbook/gitbook-plugin-fontsettings/website.css">
<link rel="stylesheet" href="gitbook/gitbook-plugin-theme-comscore/test.css">
<link rel="stylesheet" href="styles.css">
<meta name="HandheldFriendly" content="true"/>
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<link rel="apple-touch-icon-precomposed" sizes="152x152" href="gitbook/images/apple-touch-icon-precomposed-152.png">
<link rel="shortcut icon" href="gitbook/images/favicon.ico" type="image/x-icon">
<link rel="next" href="1.7.html" />
<link rel="prev" href="1.5.html" />
</head>
<body>
<div class="book">
<div class="book-summary">
<div id="book-search-input" role="search">
<input type="text" placeholder="Type to search" />
</div>
<nav role="navigation">
<ul class="summary">
<li class="chapter " data-level="1.1" data-path="index.html">
<a href="index.html">
Introduction
</a>
</li>
<li class="chapter " data-level="1.2" data-path="PA0.html">
<a href="PA0.html">
PA0 - 世界诞生的前夜: 开发环境配置
</a>
<ul class="articles">
<li class="chapter " data-level="1.2.1" data-path="0.1.html">
<a href="0.1.html">
Installing GNU/Linux
</a>
</li>
<li class="chapter " data-level="1.2.2" data-path="0.2.html">
<a href="0.2.html">
First Exploration with GNU/Linux
</a>
</li>
<li class="chapter " data-level="1.2.3" data-path="0.3.html">
<a href="0.3.html">
Installing Tools
</a>
</li>
<li class="chapter " data-level="1.2.4" data-path="0.4.html">
<a href="0.4.html">
Configuring vim
</a>
</li>
<li class="chapter " data-level="1.2.5" data-path="0.5.html">
<a href="0.5.html">
More Exploration
</a>
</li>
<li class="chapter " data-level="1.2.6" data-path="0.6.html">
<a href="0.6.html">
Getting Source Code for PAs
</a>
</li>
</ul>
</li>
<li class="chapter " data-level="1.3" data-path="PA1.html">
<a href="PA1.html">
PA1 - 开天辟地的篇章: 最简单的计算机
</a>
<ul class="articles">
<li class="chapter " data-level="1.3.1" data-path="1.1.html">
<a href="1.1.html">
在开始愉快的PA之旅之前
</a>
</li>
<li class="chapter " data-level="1.3.2" data-path="1.2.html">
<a href="1.2.html">
开天辟地的篇章
</a>
</li>
<li class="chapter " data-level="1.3.3" data-path="1.3.html">
<a href="1.3.html">
RTFSC
</a>
</li>
<li class="chapter " data-level="1.3.4" data-path="1.4.html">
<a href="1.4.html">
基础设施
</a>
</li>
<li class="chapter " data-level="1.3.5" data-path="1.5.html">
<a href="1.5.html">
表达式求值
</a>
</li>
<li class="chapter active" data-level="1.3.6" data-path="1.6.html">
<a href="1.6.html">
监视点
</a>
</li>
<li class="chapter " data-level="1.3.7" data-path="1.7.html">
<a href="1.7.html">
如何阅读手册
</a>
</li>
</ul>
</li>
<li class="chapter " data-level="1.4" data-path="PA2.html">
<a href="PA2.html">
PA2 - 简单复杂的机器: 冯诺依曼计算机系统
</a>
<ul class="articles">
<li class="chapter " data-level="1.4.1" data-path="2.1.html">
<a href="2.1.html">
不停计算的机器
</a>
</li>
<li class="chapter " data-level="1.4.2" data-path="2.2.html">
<a href="2.2.html">
RTFSC(2)
</a>
</li>
<li class="chapter " data-level="1.4.3" data-path="2.3.html">
<a href="2.3.html">
程序, 运行时环境与AM
</a>
</li>
<li class="chapter " data-level="1.4.4" data-path="2.4.html">
<a href="2.4.html">
基础设施(2)
</a>
</li>
<li class="chapter " data-level="1.4.5" data-path="2.5.html">
<a href="2.5.html">
输入输出
</a>
</li>
</ul>
</li>
<li class="chapter " data-level="1.5" data-path="PA3.html">
<a href="PA3.html">
PA3 - 穿越时空的旅程: 批处理系统
</a>
<ul class="articles">
<li class="chapter " data-level="1.5.1" data-path="3.1.html">
<a href="3.1.html">
最简单的操作系统
</a>
</li>
<li class="chapter " data-level="1.5.2" data-path="3.2.html">
<a href="3.2.html">
穿越时空的旅程
</a>
</li>
<li class="chapter " data-level="1.5.3" data-path="3.3.html">
<a href="3.3.html">
用户程序和系统调用
</a>
</li>
<li class="chapter " data-level="1.5.4" data-path="3.4.html">
<a href="3.4.html">
文件系统
</a>
</li>
<li class="chapter " data-level="1.5.5" data-path="3.5.html">
<a href="3.5.html">
精彩纷呈的应用程序
</a>
</li>
</ul>
</li>
<li class="chapter " data-level="1.6" data-path="PA4.html">
<a href="PA4.html">
PA4 - 虚实交错的魔法: 分时多任务
</a>
<ul class="articles">
<li class="chapter " data-level="1.6.1" data-path="4.1.html">
<a href="4.1.html">
多道程序
</a>
</li>
<li class="chapter " data-level="1.6.2" data-path="4.2.html">
<a href="4.2.html">
虚实交错的魔法
</a>
</li>
<li class="chapter " data-level="1.6.3" data-path="4.3.html">
<a href="4.3.html">
超越容量的界限
</a>
</li>
<li class="chapter " data-level="1.6.4" data-path="4.4.html">
<a href="4.4.html">
来自外部的声音
</a>
</li>
<li class="chapter " data-level="1.6.5" data-path="4.5.html">
<a href="4.5.html">
编写不朽的传奇
</a>
</li>
</ul>
</li>
<li class="chapter " data-level="1.7" data-path="blank.html">
<a href="blank.html">
杂项
</a>
<ul class="articles">
<li class="chapter " data-level="1.7.1" data-path="FAQ.html">
<a href="FAQ.html">
常见问题(FAQ)
</a>
</li>
<li class="chapter " data-level="1.7.2" data-path="why.html">
<a href="why.html">
为什么要学习计算机系统基础
</a>
</li>
<li class="chapter " data-level="1.7.3" data-path="linux.html">
<a href="linux.html">
Linux入门教程
</a>
</li>
<li class="chapter " data-level="1.7.4" data-path="man.html">
<a href="man.html">
man入门教程
</a>
</li>
<li class="chapter " data-level="1.7.5" data-path="git.html">
<a href="git.html">
git入门教程
</a>
</li>
<li class="chapter " data-level="1.7.6" data-path="nemu-isa-api.html">
<a href="nemu-isa-api.html">
NEMU ISA相关API说明文档
</a>
</li>
<li class="chapter " data-level="1.7.7" data-path="changelog.html">
<a href="changelog.html">
更新日志
</a>
</li>
<li class="chapter " data-level="1.7.8" data-path="i386-intro.html">
<a href="i386-intro.html">
i386手册指令集阅读指南
</a>
</li>
<li class="chapter " data-level="1.7.9" data-path="exec.html">
<a href="exec.html">
指令执行例子
</a>
</li>
</ul>
</li>
<li class="divider"></li>
<li>
<a href="https://www.gitbook.com" target="blank" class="gitbook-link">
Published with GitBook
</a>
</li>
</ul>
</nav>
</div>
<div class="book-body">
<div class="body-inner">
<div class="book-header" role="navigation">
<!-- Title -->
<h1>
<i class="fa fa-circle-o-notch fa-spin"></i>
<a href="." >监视点</a>
</h1>
</div>
<div class="page-wrapper" tabindex="-1" role="main">
<div class="page-inner">
<div id="book-search-results">
<div class="search-noresults">
<section class="normal markdown-section">
<h2 id="监视点">监视点</h2>
<p>监视点的功能是监视一个表达式的值何时发生变化.
如果你从来没有使用过监视点, 请在GDB中体验一下它的作用.</p>
<h3 id="扩展表达式求值的功能">扩展表达式求值的功能</h3>
<p>你之前已经实现了算术表达式的求值, 但这些表达式都是由常数组成的, 它们的值不会发生变化.
这样的表达式在监视点中没有任何意义, 为了发挥监视点的功能, 你首先需要扩展表达式求值的功能.</p>
<p>我们用BNF来说明需要扩展哪些功能:</p>
<pre><code><expr> ::= <decimal-number>
| <hexadecimal-number> # 以"0x"开头
| <reg_name> # 以"$"开头
| "(" <expr> ")"
| <expr> "+" <expr>
| <expr> "-" <expr>
| <expr> "*" <expr>
| <expr> "/" <expr>
| <expr> "==" <expr>
| <expr> "!=" <expr>
| <expr> "&&" <expr>
| "*" <expr> # 指针解引用
</code></pre><p>它们的功能和C语言中运算符的功能是一致的, 包括优先级和结合性, 如有疑问, 请查阅相关资料.</p>
<p>关于获取寄存器的值, 这显然是一个ISA相关的功能.
框架代码已经准备了如下的API:</p>
<pre><code>// nemu/src/isa/$ISA/reg.c
word_t isa_reg_str2val(const char *s, bool *success);
</code></pre><p>它用于返回名字为<code>s</code>的寄存器的值, 并设置<code>success</code>指示是否成功.</p>
<p>还需要注意的是指针解引用(dereference)的识别, 在进行词法分析的时候,
我们其实没有办法把乘法和指针解引用区别开来, 因为它们都是<code>*</code>.
在进行递归求值之前, 我们需要将它们区别开来,
否则如果将指针解引用当成乘法来处理的话, 求值过程将会认为表达式不合法.
其实要区别它们也不难, 给你一个表达式, 你也能将它们区别开来.
实际上, 我们只要看<code>*</code>前一个token的类型, 我们就可以决定这个<code>*</code>是乘法还是指针解引用了, 不信你试试?
我们在这里给出<code>expr()</code>函数的框架:</p>
<pre><code class="lang-c"><span class="hljs-keyword">if</span> (!make_token(e)) {
*success = <span class="hljs-literal">false</span>;
<span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;
}
<span class="hljs-comment">/* <span class="hljs-doctag">TODO:</span> Implement code to evaluate the expression. */</span>
<span class="hljs-keyword">for</span> (i = <span class="hljs-number">0</span>; i < nr_token; i ++) {
<span class="hljs-keyword">if</span> (tokens[i].type == <span class="hljs-string">'*'</span> && (i == <span class="hljs-number">0</span> || tokens[i - <span class="hljs-number">1</span>].type == certain type) ) {
tokens[i].type = DEREF;
}
}
<span class="hljs-keyword">return</span> eval(?, ?);
</code></pre>
<p>其中的<code>certain type</code>就由你自己来思考啦!
其实上述框架也可以处理负数问题, 如果你之前实现了负数, <code>*</code>的识别对你来说应该没什么困难了.</p>
<p>另外和GDB中的表达式相比, 我们做了简化, 简易调试器中的表达式没有类型之分, 因此我们需要额外说明两点:</p>
<ul>
<li>所有结果都是<code>uint32_t</code>类型.</li>
<li>指针也没有类型, 进行指针解引用的时候,
我们总是从客户计算机的内存中读出一个<code>uint32_t</code>类型的整数.</li>
</ul>
<div class="panel panel-warning"><div class="panel-heading"><h5 class="panel-title" id="扩展表达式求值的功能"><i class="fa fa-edit"></i> 扩展表达式求值的功能</h5></div><div class="panel-body"><p>你需要实现上述BNF中列出的功能.
上述BNF并没有列出C语言中所有的运算符, 例如各种位运算, <code><=</code>等等.
<code>==</code>和<code>&&</code>很可能在使用监视点的时候用到, 因此要求你实现它们.
如果你在将来的使用中发现由于缺少某一个运算符而感到使用不方便, 到时候你再考虑实现它.</p></div></div>
<!-- -->
<div class="panel panel-success"><div class="panel-heading"><h5 class="panel-title" id="riscv64中的表达式求值"><i class="fa fa-lightbulb-o"></i> riscv64中的表达式求值</h5></div><div class="panel-body"><p>由于riscv64是一个64位的ISA, 你需要把表达式求值的结果解释成<code>uint64_t</code>类型.</p></div></div>
<!-- -->
<div class="panel panel-info"><div class="panel-heading"><h5 class="panel-title" id="测试的局限"><i class="fa fa-comment-o"></i> 测试的局限</h5></div><div class="panel-body"><p>我们之前实现了一个表达式生成器, 但给表达式求值加入了寄存器使用和指针解引用这两个功能之后,
表达式生成器就不能满足我们的所有需求了.
这是因为在C程序中, 寄存器的语义并不存在, 而指针解引用的语义则与NEMU大不相同.</p><p>这里想说明的是, 测试也会有局限性, 没有一种技术可以一劳永逸地解决所有问题.
前沿的研究更是如此: 它们很多时候只能解决一小部分的问题.
然而这个表达式生成器还是给你带来了很大的信心,
去思考如何方便地测试, 哪怕是进行一部分的测试, 也是有其价值的.</p></div></div>
<h3 id="实现监视点">实现监视点</h3>
<p>简易调试器允许用户同时设置多个监视点, 删除监视点, 因此我们最好使用链表将监视点的信息组织起来.
框架代码中已经定义好了监视点的结构体(在<code>nemu/src/monitor/sdb/watchpoint.c</code>中):</p>
<pre><code class="lang-c"><span class="hljs-keyword">typedef</span> <span class="hljs-keyword">struct</span> watchpoint {
<span class="hljs-keyword">int</span> NO;
<span class="hljs-keyword">struct</span> watchpoint *next;
<span class="hljs-comment">/* <span class="hljs-doctag">TODO:</span> Add more members if necessary */</span>
} WP;
</code></pre>
<p>但结构体中只定义了两个成员: <code>NO</code>表示监视点的序号, <code>next</code>就不用多说了吧.
为了实现监视点的功能, 你需要根据你对监视点工作原理的理解在结构体中增加必要的成员.
同时我们使用"池"的数据结构来管理监视点结构体, 框架代码中已经给出了一部分相关的代码:</p>
<pre><code class="lang-c"><span class="hljs-keyword">static</span> WP wp_pool[NR_WP] = {};
<span class="hljs-keyword">static</span> WP *head = <span class="hljs-literal">NULL</span>, *free_ = <span class="hljs-literal">NULL</span>;
</code></pre>
<p>代码中定义了监视点结构的池<code>wp_pool</code>, 还有两个链表<code>head</code>和<code>free_</code>,
其中<code>head</code>用于组织使用中的监视点结构, <code>free_</code>用于组织空闲的监视点结构,
<code>init_wp_pool()</code>函数会对两个链表进行初始化.</p>
<div class="panel panel-warning"><div class="panel-heading"><h5 class="panel-title" id="实现监视点池的管理"><i class="fa fa-edit"></i> 实现监视点池的管理</h5></div><div class="panel-body"><p>为了使用监视点池, 你需要编写以下两个函数(你可以根据你的需要修改函数的参数和返回值):</p><pre><code class="lang-c"><span class="hljs-function">WP* <span class="hljs-title">new_wp</span><span class="hljs-params">()</span></span>;
<span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">free_wp</span><span class="hljs-params">(WP *wp)</span></span>;
</code></pre><p>其中<code>new_wp()</code>从<code>free_</code>链表中返回一个空闲的监视点结构, <code>free_wp()</code>将<code>wp</code>归还到<code>free_</code>链表中,
这两个函数会作为监视点池的接口被其它函数调用.
需要注意的是, 调用<code>new_wp()</code>时可能会出现没有空闲监视点结构的情况,
为了简单起见, 此时可以通过<code>assert(0)</code>马上终止程序.
框架代码中定义了32个监视点结构, 一般情况下应该足够使用,
如果你需要更多的监视点结构, 你可以修改<code>NR_WP</code>宏的值.</p><p>这两个函数里面都需要执行一些链表插入, 删除的操作,
对链表操作不熟悉的同学来说, 这可以作为一次链表的练习.</p></div></div>
<!-- -->
<div class="panel panel-info"><div class="panel-heading"><h5 class="panel-title" id="温故而知新"><i class="fa fa-question-circle"></i> 温故而知新</h5></div><div class="panel-body"><p>框架代码中定义<code>wp_pool</code>等变量的时候使用了关键字<code>static</code>,
<code>static</code>在此处的含义是什么? 为什么要在此处使用它?</p></div></div>
<p>实现了监视点池的管理之后, 我们就可以考虑如何实现监视点的相关功能了.
具体的, 你需要实现以下功能:</p>
<ul>
<li>当用户给出一个待监视表达式时, 你需要通过<code>new_wp()</code>申请一个空闲的监视点结构, 并将表达式记录下来.
然后在<code>trace_and_difftest()</code>函数(在<code>nemu/src/cpu/cpu-exec.c</code>中定义)的最后扫描所有的监视点,
每当<code>cpu_exec()</code>的循环执行完一条指令, 都会调用一次<code>trace_and_difftest()</code>函数.
在扫描监视点的过程中, 你需要对监视点的相应表达式进行求值(你之前已经实现表达式求值的功能了),
并比较它们的值有没有发生变化, 若发生了变化, 程序就因触发了监视点而暂停下来,
你需要将<code>nemu_state.state</code>变量设置为<code>NEMU_STOP</code>来达到暂停的效果.
最后输出一句话提示用户触发了监视点, 并返回到<code>sdb_mainloop()</code>循环中等待用户的命令.</li>
<li>使用<code>info w</code>命令来打印使用中的监视点信息,
至于要打印什么, 你可以参考GDB中<code>info watchpoints</code>的运行结果.</li>
<li>使用<code>d</code>命令来删除监视点, 你只需要释放相应的监视点结构即可.</li>
</ul>
<div class="panel panel-warning"><div class="panel-heading"><h5 class="panel-title" id="实现监视点"><i class="fa fa-edit"></i> 实现监视点</h5></div><div class="panel-body"><p>你需要实现上文描述的监视点相关功能, 实现了表达式求值之后, 监视点实现的重点就落在了链表操作上.</p><p>由于监视点的功能需要在<code>cpu_exec()</code>的每次循环中都进行检查, 这会对NEMU的性能带来较为明显的开销.
我们可以把监视点的检查放在<code>trace_and_difftest()</code>中, 并用一个新的宏
<code>CONFIG_WATCHPOINT</code>把检查监视点的代码包起来;
然后在<code>nemu/Kconfig</code>中为监视点添加一个开关选项,
最后通过menuconfig打开这个选项, 从而激活监视点的功能.
当你不需要使用监视点时, 可以在menuconfig中关闭这个开关选项来提高NEMU的性能.</p><p>在同一时刻触发两个以上的监视点也是有可能的, 你可以自由决定如何处理这些特殊情况, 我们对此不作硬性规定.</p></div></div>
<h2 id="调试工具与原理">调试工具与原理</h2>
<p>在实现监视点的过程中, 你很有可能会碰到段错误.
如果你因此而感觉到无助, 你应该好好阅读这一小节的内容.</p>
<p>我们来简单梳理一下段错误发生的原因.
首先, 机器永远是对的. 如果程序出了错, 先怀疑自己的代码有bug.
比如由于你的疏忽, 你编写了<code>if (p = NULL)</code>这样的代码.
但执行到这行代码的时候, 也只是<code>p</code>被赋值成<code>NULL</code>, 程序还会往下执行.
然而等到将来对<code>p</code>进行了解引用的时候, 才会触发段错误, 程序彻底崩溃.</p>
<p>我们可以从上面的这个例子中抽象出一些软件工程相关的概念:</p>
<ul>
<li>Fault: 实现错误的代码, 例如<code>if (p = NULL)</code></li>
<li>Error: 程序执行时不符合预期的状态, 例如<code>p</code>被错误地赋值成<code>NULL</code></li>
<li>Failure: 能直接观测到的错误, 例如程序触发了段错误</li>
</ul>
<p>调试其实就是从观测到的failure一步一步回溯寻找fault的过程,
找到了fault之后, 我们就很快知道应该如何修改错误的代码了.
但从上面的例子也可以看出, 调试之所以不容易, 恰恰是因为:</p>
<ul>
<li>fault不一定马上触发error</li>
<li>触发了error也不一定马上转变成可观测的failure</li>
<li>error会像滚雪球一般越积越多, 当我们观测到failure的时候, 其实已经距离fault非常遥远了</li>
</ul>
<p>理解了这些原因之后, 我们就可以制定相应的策略了:</p>
<ul>
<li>尽可能把fault转变成error.
这其实就是测试做的事情, 所以我们在上一节中加入了表达式生成器的内容,
来帮助大家进行测试, 后面的实验内容也会提供丰富的测试用例.
但并不是有了测试用例就能把所有fault都转变成error了, 因为这取决于测试的覆盖度.
要设计出一套全覆盖的测试并不是一件简单的事情, 越是复杂的系统, 全覆盖的测试就越难设计.
但是, 如何提高测试的覆盖度, 是学术界一直以来都在关注的问题.</li>
</ul>
<div class="panel panel-info"><div class="panel-heading"><h5 class="panel-title" id="你会如何测试你的监视点实现"><i class="fa fa-question-circle"></i> 你会如何测试你的监视点实现?</h5></div><div class="panel-body"><p>我们没有提供监视点相关的测试, 思考一下, 你会如何测试?</p><p>当然, 对于实验来说, 将来边用边测也是一种说得过去的方法, 就看你对自己代码的信心了.</p></div></div>
<ul>
<li>尽早观测到error的存在. 观测到error的时机直接决定了调试的难度:
如果等到触发failure的时候才发现error的存在, 调试就会比较困难;
但如果能在error刚刚触发的时候就观测到它, 调试难度也就大大降低了.
事实上, 你已经见识过一些有用的工具了:<ul>
<li><code>-Wall</code>, <code>-Werror</code>: 在编译时刻把潜在的fault直接转变成failure.
这种工具的作用很有限, 只能寻找一些在编译时刻也觉得可疑的fault, 例如<code>if (p = NULL)</code>.
不过随着编译器版本的增强, 编译器也能发现代码中的一些<a href="https://en.wikipedia.org/wiki/Undefined_behavior" target="_blank">未定义行为</a>.
这些都是免费的午餐, 不吃就真的白白浪费了.</li>
<li><code>assert()</code>: 在运行时刻把error直接转变成failure.
<code>assert()</code>是一个很简单却又非常强大的工具,
只要在代码中定义好程序应该满足的特征, 就一定能在运行时刻将不满足这些特征的error拦截下来.
例如链表的实现, 我们只需要在代码中插入一些很简单的<code>assert()</code>(例如指针解引用时不为空), 就能够几乎告别段错误.
但是, 编写这些<code>assert()</code>其实需要我们对程序的行为有一定的了解,
同时在程序特征不易表达的时候, <code>assert()</code>的作用也较为有限.</li>
<li><code>printf()</code>: 通过输出的方式观察潜在的error.
这是用于回溯fault时最常用的工具, 用于观测程序中的变量是否进入了错误的状态.
在NEMU中我们提供了输出更多调试信息的宏<code>Log()</code>, 它实际上封装了<code>printf()</code>的功能.
但由于<code>printf()</code>需要根据输出的结果人工判断是否正确, 在便利程度上相对于<code>assert()</code>的自动判断就逊色了不少.</li>
<li>GDB: 随时随地观测程序的任何状态.
调试器是最强大的工具, 但你需要在程序行为的茫茫大海中观测那些可疑的状态, 因此使用起来的代价也是最大的.</li>
</ul>
</li>
</ul>
<div class="panel panel-info"><div class="panel-heading"><h5 class="panel-title" id="强大的gdb"><i class="fa fa-question-circle"></i> 强大的GDB</h5></div><div class="panel-body"><p>如果你遇到了段错误, 你很可能会想知道究竟是哪一行代码触发了段错误.
尝试编写一个触发段错误的程序, 然后在GDB中运行它.
你发现GDB能为你提供哪些有用的信息吗?</p></div></div>
<!-- -->
<div class="panel panel-success"><div class="panel-heading"><h5 class="panel-title" id="sanitizer---一种底层的assert"><i class="fa fa-lightbulb-o"></i> sanitizer - 一种底层的assert</h5></div><div class="panel-body"><p>段错误一般是由于非法访存造成的, 一种简单的想法是,
如果我们能在每一次访存之前都用<code>assert()</code>检查一下地址是否越界,
就可以在段错误发生之前捕捉到error了!</p><p>虽然我们只需要重点关注指针和数组的访问, 但这样的代码在项目中有很多,
如果要我们手动在这些访问之前添加<code>assert()</code>, 就太麻烦了.
事实上, 最适合做这件事情的是编译器, 因为它能知道指针和数组的访问都在哪里.
而让编译器支持这个功能的是一个叫<code>Address Sanitizer</code>的工具,
它可以自动地在指针和数组的访问之前插入用来检查是否越界的代码.
GCC提供了一个<code>-fsanitize=address</code>的编译选项来启用它.
menuconfig已经为大家准备好相应选项了, 你只需要打开它:</p><pre><code>Build Options
[*] Enable address sanitizer
</code></pre><p>然后清除编译结果并重新编译即可.</p><p>你可以尝试故意触发一个段错误, 然后阅读一下Address Sanitizer的报错信息.
不过你可能会发现程序的性能有所下降, 这是因为对每一次访存进行检查会带来额外的性能开销.
但作为一个可以帮助你诊断bug的工具, 付出这一点代价还是很值得的,
而且你还是可以在无需调试的时候将其关闭.</p><p>事实上, 除了地址越界的错误之外, Address Sanitizer还能检查use-after-free的错误
(即"释放从堆区申请的空间后仍然继续使用"的错误), 你知道它是如何实现这一功能的吗?</p></div></div>
<!-- -->
<div class="panel panel-info"><div class="panel-heading"><h5 class="panel-title" id="readline库中的内存泄漏"><i class="fa fa-comment-o"></i> readline库中的内存泄漏</h5></div><div class="panel-body"><p>经同学探究发现, <code>readline</code>库在8.2版本之前存在内存泄漏,
开启Address Sanitizer后可能检测出该问题:</p><pre><code>==<pid>==ERROR: LeakSanitizer: detected memory leaks
Direct leak of 32 byte(s) in 1 object(s) allocated from:
#0 0x7f55100b4887 in __interceptor_malloc ../../../../src/libsanitizer/asan/asan_malloc_linux.cpp:145
#1 0x7f5510b4fbac in xmalloc (/lib/x86_64-linux-gnu/libreadline.so.8+0x39bac)
SUMMARY: AddressSanitizer: 32 byte(s) leaked in 1 allocation(s).
</code></pre><p>如果你从Address Sanitizer的报错信息中发现问题出在<code>libreadline.so</code>等文件中,
说明该内存泄漏可能不是由你的代码引起.
但这个问题不影响NEMU的正确性, 因此你可以忽略它.</p><p>更多信息可参考<a href="https://blog.rijuyuezhu.top/posts/972c44eb/" target="_blank">这篇博客文章</a>.</p></div></div>
<div class="panel panel-success"><div class="panel-heading"><h5 class="panel-title" id="让address-sanitizer输出更精确的出错位置"><i class="fa fa-lightbulb-o"></i> 让Address Sanitizer输出更精确的出错位置</h5></div><div class="panel-body"><p>如果在添加GDB调试信息的情况下打开Address Sanitizer,
其报错信息还会指出发生错误的具体代码位置, 为问题的定位提供便利.</p></div></div>
<!-- -->
<div class="panel panel-success"><div class="panel-heading"><h5 class="panel-title" id="更多的sanitizer"><i class="fa fa-lightbulb-o"></i> 更多的sanitizer</h5></div><div class="panel-body"><p>事实上, GCC还支持更多的sanitizer, 它们可以检查各种不同的错误,
你可以在<code>man gcc</code>中查阅<code>-fsanitize</code>相关的选项.
如果你的程序在各种sanitizer开启的情况下仍然能正确工作,
就说明你的程序还是有一定质量的.</p></div></div>
<p>根据上面的分析, 我们就可以总结出一些调试的建议:</p>
<ul>
<li>总是使用<code>-Wall</code>和<code>-Werror</code></li>
<li>尽可能多地在代码中插入<code>assert()</code></li>
<li>调试时先启用sanitizer</li>
<li><code>assert()</code>无法捕捉到error时, 通过<code>printf()</code>输出可疑的变量, 期望能观测到error</li>
<li><code>printf()</code>不易观测error时, 通过GDB理解程序的精确行为</li>
</ul>
<p>如果你在程序设计课上听说过上述这些建议, 相信你几乎不会遇到运行时错误.</p>
<h2 id="断点">断点</h2>
<p>断点的功能是让程序暂停下来, 从而方便查看程序某一时刻的状态.
事实上, 我们可以很容易地用监视点来模拟断点的功能:</p>
<pre><code>w $pc == ADDR
</code></pre><p>其中<code>ADDR</code>为设置断点的地址.
这样程序执行到<code>ADDR</code>的位置时就会暂停下来.</p>
<div class="panel panel-info"><div class="panel-heading"><h5 class="panel-title" id="如何提高断点的效率-建议二周目思考"><i class="fa fa-question-circle"></i> 如何提高断点的效率 (建议二周目思考)</h5></div><div class="panel-body"><p>如果你在运行稍大一些的程序(如microbench)的时候使用断点,
你会发现设置断点之后会明显地降低NEMU执行程序的效率.
思考一下这是为什么? 有什么方法解决这个问题吗?</p></div></div>
<p>调试器设置断点的工作方式和上述通过监视点来模拟断点的方法大相径庭.
事实上, 断点的工作原理, 竟然是三十六计之中的"偷龙转凤"!
如果你想揭开这一神秘的面纱, 你可以阅读<a href="http://eli.thegreenplace.net/2011/01/27/how-debuggers-work-part-2-breakpoints" target="_blank">这篇文章</a>.
了解断点的工作原理之后, 可以尝试思考下面的两个问题.</p>
<div class="panel panel-info"><div class="panel-heading"><h5 class="panel-title" id="一点也不能长"><i class="fa fa-question-circle"></i> 一点也不能长?</h5></div><div class="panel-body"><p>x86的<code>int3</code>指令不带任何操作数, 操作码为1个字节, 因此指令的长度是1个字节. 这是必须的吗?
假设有一种x86体系结构的变种my-x86, 除了<code>int3</code>指令的长度变成了2个字节之外, 其余指令和x86相同.
在my-x86中, 上述文章中的断点机制还可以正常工作吗? 为什么?</p></div></div>
<!-- -->
<div class="panel panel-info"><div class="panel-heading"><h5 class="panel-title" id="随心所欲的断点"><i class="fa fa-question-circle"></i> 随心所欲的断点</h5></div><div class="panel-body"><p>如果把断点设置在指令的非首字节(中间或末尾), 会发生什么?
你可以在GDB中尝试一下, 然后思考并解释其中的缘由.</p></div></div>
<!-- -->
<div class="panel panel-info"><div class="panel-heading"><h5 class="panel-title" id="nemu的前世今生"><i class="fa fa-question-circle"></i> NEMU的前世今生</h5></div><div class="panel-body"><p>你已经对NEMU的工作方式有所了解了.
事实上在NEMU诞生之前, NEMU曾经有一段时间并不叫NEMU,
而是叫NDB(NJU Debugger), 后来由于某种原因才改名为NEMU.