-
Notifications
You must be signed in to change notification settings - Fork 154
/
Copy path2.4.html
1487 lines (969 loc) · 134 KB
/
2.4.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>基础设施(2) · 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="2.5.html" />
<link rel="prev" href="2.3.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 " 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 active" 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="." >基础设施(2)</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="基础设施2">基础设施(2)</h2>
<h3 id="bug诊断的利器---踪迹">bug诊断的利器 - 踪迹</h3>
<p>我们已经知道程序是个状态机, 如果程序变得复杂, 这个状态机的转移情况也会变得复杂.
先不说状态机中每个状态的细节, 就连状态机进行了怎么样的转移, 我们也很难完全搞清楚.</p>
<p>如果通过GDB来了解这一过程, 效率就会显得有点低了.
为了提高效率, 我们可以通过<code>printf()</code>来输出我们关心的某些信息.
我们关心程序这个状态机转移的细节, 也说明我们关心的是程序的执行过程.
在软件工程领域, 记录程序执行过程的信息称为<a href="https://en.wikipedia.org/wiki/Tracing_(software)" target="_blank">踪迹(trace)</a>.
有了踪迹信息, 我们就可以判断程序的执行过程是否符合预期, 从而进行bug的诊断.</p>
<h4 id="指令执行的踪迹---itrace">指令执行的踪迹 - itrace</h4>
<p>NEMU已经实现了一个简单的踪迹功能 -- itrace (instruction trace), 它可以记录客户程序执行的每一条指令.
itrace的实现很简单, 代码只要记录<code>inst_fetch()</code>取到的每一条指令,
然后调用<a href="https://github.com/capstone-engine/capstone/" target="_blank">capstone项目</a>提供的反汇编功能(在<code>nemu/src/utils/disasm.c</code>中实现).
itrace会输出指令的PC, 二进制表示以及反汇编结果.
框架代码默认已经打开了这个功能, 客户程序执行的指令都会被记录到<code>build/nemu-log.txt</code>中.
查看这个文件, 你就可以知道客户程序是如何运行的了.</p>
<p>NEMU可以限制trace输出的时机, 你可以手动指定什么时候才输出它们,
甚至还可以自定义输出trace的条件. 具体如何指定, RTFSC.
由于目前程序的行为都是确定的, 多次运行会得到相同的结果,
这对我们了解程序什么时候出错是很有帮助的.</p>
<p>对于一些输出规整的trace, 我们还可以通过<code>grep</code>, <code>awk</code>, <code>sed</code>等文本处理工具来对它们进行筛选和处理.
因此如果你掌握一些用于文本处理的shell命令, 你就可以进一步提高调试的效率了.</p>
<h4 id="指令环形缓冲区---iringbuf">指令环形缓冲区 - iringbuf</h4>
<p>一般来说, 我们只会关心出错现场前的trace, 在运行一些大程序的时候,
运行前期的trace大多时候没有查看甚至输出的必要.
一个很自然的想法就是, 我们能不能在客户程序出错(例如访问物理内存越界)的时候输出最近执行的若干条指令呢?</p>
<p>要实现这个功能其实并不困难, 我们只需要维护一个很简单的数据结构 - 环形缓冲区(ring buffer)即可.
具体地, 在每执行一条指令的时候, 就把这条指令的信息写入到环形缓冲区中;
如果缓冲区满了, 就会覆盖旧的内容. 客户程序出错的时候, 就把环形缓冲区中的指令打印出来, 供调试进行参考.
一个输出的示例如下, 其中<code>--></code>指示了出错的指令:</p>
<pre><code> 0x80002b00: srli a2, a2, 1 00 16 56 13
0x80002b04: slli a1, a1, 1 00 15 95 93
0x80002b08: or a0, s1, a5 00 f4 e5 33
0x80002b0c: sub s0, s0, a5 40 f4 04 33
0x80002b10: jal -112 f9 1f f0 ef
0x80002aa0: lui a5, 524295 80 00 77 b7
0x80002aa4: lw a5, 2028(a5) 7e c7 a7 83
0x80002aa8: addi sp, sp, -32 fe 01 01 13
--> 0x80002aac: sw s2, 16(sp) 01 21 28 23
0x80002b3c: ret 00 00 80 67
0x80002b14: add s2, s2, a0 00 a9 09 33
0x80002b18: bnez s0, -40 fc 04 1c e3
0x80002af0: neg a5, s0 40 80 07 b3
0x80002af4: and a5, a5, s0 00 87 f7 b3
0x80002af8: or a2, s3, a5 00 f9 e6 33
0x80002afc: or a1, s4, a5 00 fa 65 b3
</code></pre><div class="panel panel-warning"><div class="panel-heading"><h5 class="panel-title" id="实现iringbuf"><i class="fa fa-edit"></i> 实现iringbuf</h5></div><div class="panel-body"><p>根据上述内容, 在NEMU中实现iringbuf. 你可以按照自己的喜好来设计输出的格式,
如果你想输出指令的反汇编, 可以参考itrace的相关代码;
如果你不知道应该在什么地方添加什么样的代码, 你就需要RTFSC了.</p></div></div>
<h4 id="内存访问的踪迹---mtrace">内存访问的踪迹 - mtrace</h4>
<p>访问内存占程序执行很大的一部分, 如果你遇到过一些和访存相关的错误(例如物理内存越界),
你一定想知道程序访存的具体行为, 然后从其中找出不正确的访存, 从而帮助你进行bug的诊断.
事实上, 我们可以很容易地对访存的结果进行追踪, 从而记录访存的踪迹(memory trace).</p>
<div class="panel panel-warning"><div class="panel-heading"><h5 class="panel-title" id="实现mtrace"><i class="fa fa-edit"></i> 实现mtrace</h5></div><div class="panel-body"><p>这个功能非常简单, 你已经想好如何实现了:
只需要在<code>paddr_read()</code>和<code>paddr_write()</code>中进行记录即可.
你可以自行定义mtrace输出的格式.</p><p>不过和最后只输出一次的iringbuf不同, 程序一般会执行很多访存指令,
这意味着开启mtrace将会产生大量的输出, 因此最好可以在不需要的时候关闭mtrace.
噢, 那就参考一下itrace的相关实现吧: 尝试在Kconfig和相关文件中添加相应的代码,
使得我们可以通过menuconfig来打开或者关闭mtrace.
另外也可以实现mtrace输出的条件, 例如你可能只会关心某一段内存区间的访问,
有了相关的条件控制功能, mtrace使用起来就更加灵活了.</p></div></div>
<h4 id="函数调用的踪迹---ftrace">函数调用的踪迹 - ftrace</h4>
<p>itrace和mtrace都是从底层状态机视角来追踪程序的运行,
但如果我们想了解程序的语义行为, itrace和mtrace就无法帮助我们了,
因此我们需要一个带有程序语义行为trace工具.</p>
<p>问题是, 我们应该选择怎么样的语义呢?
在程序设计课上, 我们知道一个程序由若干函数构成, 而一个函数则是由若干语句构成,
一条语句又会被编译成若干指令, 因此, 可以清晰地携带程序语义的就只有函数了.
想象一下, 如果我们能设计一个工具ftrace, 用来追踪程序执行过程中的函数调用和返回,
我们不就可以知道程序大概是如何工作的了吗?</p>
<p>这其实并不困难, 因为itrace已经能够追踪程序执行的所有指令了,
要实现ftrace, 我们只需要关心函数调用和返回相关的指令就可以了.
我们可以在函数调用指令中记录目标地址, 表示将要调用某个函数;
然后在函数返回指令中记录当前PC, 表示将要从PC所在的函数返回.
我们很容易在相关指令的实现中添加代码来实现这些功能.
但目标地址和PC值仍然缺少程序语义, 如果我们能把它们翻译成函数名, 就更容易理解了!</p>
<p>给定一个位于代码段的地址, 如何得知它位于哪一个函数呢?
这就需要ELF文件中符号表(symbol table)的帮助了.
符号表是可执行文件的一个section, 它记录了程序编译时刻的一些信息, 其中就包括变量和函数的信息.
为了实现ftrace, 我们首先需要了解符号表中都记录了哪些信息.</p>
<p>以<code>cpu-tests</code>中<code>add</code>这个用户程序为例, 使用readelf命令查看ELF可执行文件的信息:</p>
<pre><code class="lang-bash">riscv64-linux-gnu-readelf <span class="hljs-_">-a</span> add-riscv32-nemu.elf
</code></pre>
<p>你会看到readelf命令输出了很多信息, 这些信息对了解ELF的结构有很好的帮助, 我们建议你在课后仔细阅读.
目前我们只需要关心符号表的信息就可以了, 在输出中找到符号表的信息:</p>
<pre><code>Symbol table '.symtab' contains 28 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 00000000 0 NOTYPE LOCAL DEFAULT UND
1: 80000000 0 SECTION LOCAL DEFAULT 1
2: 80000108 0 SECTION LOCAL DEFAULT 2
3: 8000010c 0 SECTION LOCAL DEFAULT 3
4: 8000020c 0 SECTION LOCAL DEFAULT 4
5: 00000000 0 SECTION LOCAL DEFAULT 5
6: 00000000 0 FILE LOCAL DEFAULT ABS add.c
7: 00000000 0 FILE LOCAL DEFAULT ABS trm.c
8: 80000108 1 OBJECT LOCAL DEFAULT 2 mainargs
9: 800000e8 32 FUNC GLOBAL DEFAULT 1 _trm_init
10: 80009000 0 NOTYPE GLOBAL DEFAULT 4 _stack_pointer
11: 80000108 0 NOTYPE GLOBAL DEFAULT 1 _etext
12: 80000000 0 NOTYPE GLOBAL DEFAULT ABS _pmem_start
13: 8000022c 0 NOTYPE GLOBAL DEFAULT 4 _bss_start
14: 80000109 0 NOTYPE GLOBAL DEFAULT 2 edata
15: 80009000 0 NOTYPE GLOBAL DEFAULT 4 _heap_start
16: 80001000 0 NOTYPE GLOBAL DEFAULT 4 _stack_top
17: 80009000 0 NOTYPE GLOBAL DEFAULT 4 end
18: 80000010 24 FUNC GLOBAL DEFAULT 1 check
19: 80000108 0 NOTYPE GLOBAL DEFAULT 1 etext
20: 80000000 0 FUNC GLOBAL DEFAULT 1 _start
21: 00000000 0 NOTYPE GLOBAL DEFAULT ABS _entry_offset
22: 80000028 180 FUNC GLOBAL DEFAULT 1 main
23: 80000109 0 NOTYPE GLOBAL DEFAULT 2 _data
24: 8000010c 256 OBJECT GLOBAL DEFAULT 3 ans
25: 80009000 0 NOTYPE GLOBAL DEFAULT 4 _end
26: 800000dc 12 FUNC GLOBAL DEFAULT 1 halt
27: 8000020c 32 OBJECT GLOBAL DEFAULT 4 test_data
</code></pre><p>其中每一行代表一个表项, 每一列列出了表项的一些属性,
现在我们只需要关心<code>Type</code>属性为<code>FUNC</code>的表项就可以了.
仔细观察<code>Name</code>属性之后, 你会发现这些表项正好对应了程序中定义的函数,
相应的<code>Value</code>属性正好是它们的起始地址(你可以与反汇编结果进行对比),
而相应的<code>Size</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>我们在<code>am-kernels/tests/cpu-tests/tests/add.c</code>中定义了宏<code>NR_DATA</code>,
同时也在<code>add()</code>函数中定义了局部变量<code>c</code>和形参<code>a</code>, <code>b</code>,
但你会发现在符号表中找不到和它们对应的表项, 为什么会这样?
思考一下, 什么才算是一个符号(symbol)?</p></div></div>
<p>噢, 通过符号表, 我们可以建立函数名与其地址之间的映射关系!
但readelf输出的信息是已经经过解析的, 实际上符号表中<code>Name</code>
属性存放的是字符串在字符串表(string table)中的偏移量.
为了查看字符串表, 我们先查看readelf输出中Section Headers的信息:</p>
<pre><code>Section Headers:
[Nr] Name Type Addr Off Size ES Flg Lk Inf Al
[ 0] NULL 00000000 000000 000000 00 0 0 0
[ 1] .text PROGBITS 80000000 001000 000108 00 AX 0 0 4
[ 2] .sdata2.mainargs PROGBITS 80000108 001108 000001 00 A 0 0 4
[ 3] .data.ans PROGBITS 8000010c 00110c 000100 00 WA 0 0 4
[ 4] .data.test_data PROGBITS 8000020c 00120c 000020 00 WA 0 0 4
[ 5] .comment PROGBITS 00000000 00122c 00001c 01 MS 0 0 1
[ 6] .symtab SYMTAB 00000000 001248 0001c0 10 7 9 4
[ 7] .strtab STRTAB 00000000 001408 00009b 00 0 0 1
[ 8] .shstrtab STRTAB 00000000 0014a3 000055 00 0 0 1
</code></pre><p>从Section Headers的信息可以看到, 字符串表在ELF文件偏移为<code>0x1408</code>的位置开始存放.
我们可以通过以下命令直接输出ELF文件的十六进制形式:</p>
<pre><code class="lang-bash">hd add-riscv32-nemu.elf
</code></pre>
<p>查看这一命令输出结果中<code>0x1408</code>附近的部分, 我们可以看到,
字符串表只不过是把标识符的字符串拼接起来而已.
现在我们就可以厘清符号表和字符串表之间的关系了:</p>
<pre><code>Section Headers:
[Nr] Name Type Addr Off Size ES Flg Lk Inf Al
[ 7] .strtab STRTAB 00000000 001408 00009b 00 0 0 1
|
+--------------+
++ V
00001400 |V 00 61 64 64 2e 63 00 74 | ........add.c.t|
00001410 72 6d 2e 63 00|6d 61 69 6e 61 72 67 73 00 5f 74 |rm.c.mainargs._t|
00001420 72 6d 5f 69 6e|69 74 00 ^ |rm_init._stack_p|
| |
| +----------------+
| |
+-----------------------------------------+ |
Symbol table '.symtab' contains 10 entries: | |
Num: Value Size Type Bind Vis Ndx Name | |
7: 00000000 0 FILE LOCAL DEFAULT ABS 7 (trm.c) | |
8: 80000108 1 OBJECT LOCAL DEFAULT 2 13(mainargs) --+ |
9: 800000e8 32 FUNC GLOBAL DEFAULT 1 22(_trm_init) -----+
</code></pre><div class="panel panel-info"><div class="panel-heading"><h5 class="panel-title" id="寻找hello-world"><i class="fa fa-question-circle"></i> 寻找"Hello World!"</h5></div><div class="panel-body"><p>在Linux下编写一个Hello World程序, 编译后通过上述方法找到ELF文件的字符串表,
你发现"Hello World!"字符串在字符串表中的什么位置? 为什么会这样?</p></div></div>
<p>现在我们就可以把一个给定的地址翻译成函数名了:
由于函数的范围是互不相交的, 我们可以逐项扫描符号表中<code>Type</code>属性为<code>FUNC</code>的每一个表项,
检查给出的地址是否落在区间<code>[Value, Value + Size)</code>内,
若是, 则根据表项中的<code>Name</code>属性在字符串表中找到相应的字符串, 作为函数名返回.
如果没有找到符合要求的符号表表项, 可以返回字符串"???",
不过这很可能是你的实现错误导致的, 你需要再次检查你的实现.</p>
<p>以运行<code>cpu-tests</code>中的<code>recursion</code>为例, ftrace的部分示例输出如下
(仅供参考, 不同的编译器版本可能会得到不同的输出, 你可以结合反汇编结果进行理解):</p>
<pre><code>0x8000000c: call [_trm_init@0x80000260]
0x80000270: call [main@0x800001d4]
0x800001f8: call [f0@0x80000010]
0x8000016c: call [f2@0x800000a4]
0x800000e8: call [f1@0x8000005c]
0x8000016c: call [f2@0x800000a4]
0x800000e8: call [f1@0x8000005c]
0x8000016c: call [f2@0x800000a4]
0x800000e8: call [f1@0x8000005c]
0x8000016c: call [f2@0x800000a4]
0x800000e8: call [f1@0x8000005c]
0x8000016c: call [f2@0x800000a4]
0x800000e8: call [f1@0x8000005c]
0x80000058: ret [f0] # 注释(2)
0x800000fc: ret [f2] # 注释(1)
0x80000180: call [f2@0x800000a4]
0x800000e8: call [f1@0x8000005c]
0x80000058: ret [f0]
0x800000fc: ret [f2]
0x800001b0: ret [f3] # 注释(3)
0x800000fc: ret [f2]
0x80000180: call [f2@0x800000a4]
0x800000e8: call [f1@0x8000005c]
0x8000016c: call [f2@0x800000a4]
0x800000e8: call [f1@0x8000005c]
0x80000058: ret [f0]
</code></pre><div class="panel panel-warning"><div class="panel-heading"><h5 class="panel-title" id="实现ftrace"><i class="fa fa-edit"></i> 实现ftrace</h5></div><div class="panel-body"><p>根据上述内容, 在NEMU中实现ftrace. 你可以自行决定输出的格式. 你需要注意以下内容:</p><ul>
<li>你需要为NEMU传入一个ELF文件, 你可以通过在<code>parse_args()</code>中添加相关代码来实现这一功能</li>
<li>你可能需要在初始化ftrace时从ELF文件中读出符号表和字符串表, 供你后续使用</li>
<li>关于如何解析ELF文件, 可以参考<code>man 5 elf</code></li>
<li>如果你选择的是riscv32, 你还需要考虑如何从<code>jal</code>和<code>jalr</code>指令中正确识别出函数调用指令和函数返回指令</li>
</ul><p>注意, 你不应该通过<code>readelf</code>等工具直接解析ELF文件. 在真实的项目中, 这个方案确实可以解决问题;
但作为一道学习性质的题目, 其目标是让你了解ELF文件的组织结构,
使得将来你在必要的时候(例如在裸机环境中)可以自己从中解析出所需的信息.
如果你通过<code>readelf</code>等工具直接解析ELF文件, 相当于自动放弃训练的机会,
与我们设置这道题目的目的背道而驰.</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>recursion</code>的示例输出, 你会发现一些有趣的现象.
具体地, 注释(1)处的<code>ret</code>的函数是和对应的<code>call</code>匹配的, 也就是说,
<code>call</code>调用了<code>f2</code>, 而与之对应的<code>ret</code>也是从<code>f2</code>返回;
但注释(2)所指示的一组<code>call</code>和<code>ret</code>的情况却有所不同,
<code>call</code>调用了<code>f1</code>, 但却从<code>f0</code>返回;
注释(3)所指示的一组<code>call</code>和<code>ret</code>也出现了类似的现象,
<code>call</code>调用了<code>f1</code>, 但却从<code>f3</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>在Linux下编写一个Hello World程序, 然后使用<code>strip</code>命令丢弃可执行文件中的符号表:</p><pre><code class="lang-bash">gcc -o hello hello.c
strip <span class="hljs-_">-s</span> hello
</code></pre><p>用<code>readelf</code>查看hello的信息, 你会发现符号表被丢弃了, 此时的hello程序能成功运行吗?</p><p>目标文件中也有符号表, 我们同样可以丢弃它:</p><pre><code class="lang-bash">gcc -c hello.c
strip <span class="hljs-_">-s</span> hello.o
</code></pre><p>用<code>readelf</code>查看hello.o的信息, 你会发现符号表被丢弃了. 尝试对hello.o进行链接:</p><pre><code class="lang-bash">gcc -o hello hello.o
</code></pre><p>你发现了什么问题? 尝试对比上述两种情况, 并分析其中的原因.</p></div></div>
<!-- -->
<div class="panel panel-info"><div class="panel-heading"><h5 class="panel-title" id="trace与性能优化"><i class="fa fa-comment-o"></i> trace与性能优化</h5></div><div class="panel-body"><p>我们让大家在NEMU实现trace工具, 是作为一种基础设施来帮助大家进行调试.
事实上, trace除了可以帮助大家认识程序如何运行之外,
还可以指导开发者进行程序和系统的优化, 例如:</p><ul>
<li>可以基于ftrace进一步分析出调用<code>memcpy()</code>时的参数情况, 比如<code>dest</code>和<code>src</code>是否对齐,
拷贝的内存长度是长还是短, 然后根据频繁出现的组合对<code>memcpy()</code>的算法实现进行优化</li>
<li>可以基于ftrace统计函数调用的次数, 对访问次数较多的函数进行优化, 可以显著提升程序的性能</li>
<li>可以基于itrace过滤出分支跳转指令的执行情况,
作为分支预测器(现代处理器中的一个提升性能的部件)的输入,
来调整分支预测器的实现, 从而提升处理器的性能</li>
<li>可以基于mtrace得到程序的访存序列, 作为缓存(现代处理器中的另一个提升性能的部件)模型的输入,
对预取算法和替换算法的优化进行指导(你将会在Lab4中体会这一点)</li>
</ul><p>trace对性能优化来说如此重要, 是因为trace反映了程序运行的真实行为,
如果你拍脑袋优化了程序中一个只会调用1次的函数, 可以想象这样的优化对程序总体性能几乎没有任何提升.
而trace其实向我们展示了程序运行过程中的细节事件,
如果我们对这些事件进行统计意义上的分析, 我们就可以知道哪些事件才是频繁发生的,
而优化这些频繁发生的事件, 才能从统计意义上提升程序和系统的性能, 这才是性能优化的科学方法.</p></div></div>
<h3 id="am作为基础设施">AM作为基础设施</h3>
<p>编写klib, 然后在NEMU上运行<code>string</code>程序, 看其是否能通过测试.
表面上看, 这个做法似乎没什么不妥当, 然而如果测试不通过,
你在调试的时候肯定会思考: 究竟是klib写得不对, 还是NEMU有bug呢?