PA W5 - NEMU framework
Warning
If someone is reading this blog, please be aware that the writer did not consider the experience of the other readers.
After all, the most important part is about writing things down for better memorization.
Building Project: Using Make
When a project grows to a very large scale, even just using
gcc
from the command line a few times will become painful and cumbersome as there might be tens of commands to execute. Not to mention that the whole thing needs to be done again and again during development.To resolve this dilemma, we can use a build system called
make
.make
usesMakefile
to build the project.Makefile
is a special file which contains declarative code to describe the dependent relationship between the build targets and ways to update them.It can automatically detect the files which need to be updated, avoiding repeatitive build.
Tricks When Studying Behaviors of
Make
:-n
: Dry-run, simulate the process of build without actually running it, and prints out all the commands.-B
: Forcegcc
to update all of the make target.
Combined(transmit the output of
grep
tonvim
by using pipeline|
):1
2
3make -nB \
| grep -ve '^\(\#\|echo\|mkdir\)' \
| nvim -
Reading Makefile in Browser
Use this command to convert a
Makefile
into ahtml
file:1
2
3### *Get a more readable version of this Makefile* by `make html` (requires python-markdown)
html:
cat Makefile | sed 's/^\([^#]\)/ \1/g' | markdown_py > Makefile.htmlNB: command
markdown_py
is provided by packagepython-markdwon
.
A Simple main Function:
main
function in nemu:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
void init_monitor(int, char *[]);
void am_init_monitor();
void engine_start();
int is_exit_status_bad();
int main(int argc, char *argv[]) {
/* Initialize the monitor. */
am_init_monitor();
init_monitor(argc, argv);
/* Start engine. */
engine_start();
return is_exit_status_bad();
}We can see that this main function only serves as a director–it only calls other functions without executing any actual logic of the project.
Some Code Explanation
getopt_long
1 | static int parse_args(int argc, char *argv[]) { |
- The
getopt_long
function is used to parse command-line options, especially for parsing long arguments. - Check the detailed definition and usage with
man 3 getopt_long
.
static
Take a look at this function:
1
2
3
4
5
6
7static void restart() {
/* Set the initial program counter. */
cpu.pc = RESET_VECTOR;
/* The zero register is always 0. */
cpu.gpr[0] = 0;
}We can see that this function is a
static
one. But what doesstatic
mean?static (C99 6.2.2 #3): If the declaration of a file scope identifier for an object or a function contains the storage- class specifier static, the identifier has internal linkage.
This explanation is very abstract and hard to understand for me as I don’t have much knowledge about it. Let’s take a look at an example of it:
AWAK, in C if we have several functions with the same name declared in different files, these files can be compiled separately. But when it comes to linkage, it will cause errors.
1
2/* a.c */ int f() { return 0; }
/* b.c */ int f() { return 1; }Will cause:
1
b.c:(.text+0x0): multiple definition of f; a.c:(.text+0xb): first defined here
This is also the reason why we shouldn’t write definitions of functions into head files, since a head file could be possibly included multiple times in different source files.
Somebody may argue that ‘no one does this, it’s just stupid’. Well, there are actually multiple functions were declared also defined in head files in PA. Therefore, they need the
static
specifier, making the corresponding identifier to have internal linkage and avoid the errors.
inline
Further more, if we take a look to a function written in
reg.h
, we would find that it also has a specifier calledinline
:1
2
3static inline int check_reg_idx(int idx) {
...
}It turns out to be another reminder for the compiler, telling the compiler that, for every call for this function, the function call will be replaced with the actual code of the function, rather than just pushing the original loaded function into stack.
The purpose of doing so is to improve performance by eliminating the overhead of a function call, at a price of an increased code size.
The
inline
function shouldn’t be too long(large) as it would increase size of the code.
Assert
In nemu project,
Assert
is actually a macro defined in the framework, see below:1
2
3
4
5
6
7
8
9
10
11
12
13
14
Log && ASNI_FMT
In
monitor.c
, functionwelcome
:1
2Log("Trace: %s", MUXDEF(CONFIG_TRACE, ANSI_FMT("ON", ANSI_FG_GREEN),
ANSI_FMT("OFF", ANSI_FG_RED)));Log
:This macro can be used to print out log info and write them into specified log files automatically.
debug.h
:1
2
3utils.h
:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
ASNI_FMT
:This macro is used to define the foreground & background of the information printed out to the command line. Namely, customize the theme of the output.
utils.h
:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19Effect:
Debugging core dump: Segmentation Fault
- Sometimes(like
core dump
) it is hard to debug nemu, since you might not know whether the error occurs in nemu itself or the program which it is running. - In this situation, we would need to use the
backtrace
feature ofgdb
to go back to the frame which causes the problem.
Expanding Macros in a Better Way
There are many nested macros in the source code of PA. Expanding them manually doesn’t seem to be a good idea.
We can add these lines to the Makefile(
build.mk
):1
2
3
4
5
6
7
8
9
10
11
12
13
14# Compilation patterns
$(OBJ_DIR)/%.o: %.c
@echo + CC $<
@mkdir -p $(dir $@)
@$(CC) $(CFLAGS) -c -o $@ $<
@# -----Expand macros-----
@$(CC) $(CFLAGS) -E -MF /dev/null $@ $< | \
grep -ve '^#' | \
clang-format - > \
$(basename $@).i
@# -----Expand macros-----
$(call call_fixdep, $(@:.o=.d), $@)Though, I think the output file of this command is even harder to read…
End.
- Title: PA W5 - NEMU framework
- Author: Last
- Created at : 2024-06-17 16:07:49
- Link: https://blog.imlast.top/2024/06/17/nju-pa-w5/
- License: This work is licensed under CC BY-NC-SA 4.0.