You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: _posts/2019-04-27-riscv-from-scratch-2.markdown
+30-20Lines changed: 30 additions & 20 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -19,14 +19,31 @@ The `freedom-e-sdk` made it trivial for us to compile, debug, and run any C prog
19
19
20
20
In this post, we'll break free from the `freedom-e-sdk`. We'll write and attempt to debug a simple C program of our own, unveil the magic hidden behind `main`, and examine the hardware layout of a `qemu` virtual machine. We'll then examine and modify a linker script, write our own C runtime to get our program set up and running, and finally invoke GDB and step through our program.
21
21
22
+
### Setup
23
+
22
24
If you missed the previous post in this series and don't have `riscv-qemu` and the RISC-V toolchain installed and were hoping to follow along, jump to the ["QEMU and RISC-V toolchain setup"](/riscv-from-scratch/2019/03/10/riscv-from-scratch-1.html#qemu-and-risc-v-toolchain-setup) section (or in RISC-V assembly, `jal x0, qemu_and_toolchain_setup`) and complete that before moving on.
23
25
26
+
Next, let's set up a workspace for the code we will write today (and in future posts).
# or `git clone https://github.com/twilco/riscv-from-scratch.git` to clone
31
+
# via HTTPS rather than SSH
32
+
# alternatively, if you are a GitHub user, you can fork this repo.
33
+
# https://help.github.com/en/articles/fork-a-repo
34
+
35
+
cd riscv-from-scratch/work
36
+
{% endhighlight %}
37
+
38
+
As the name suggests, the `work` directory will serve as our working directory for this and future posts.
39
+
24
40
### The naive approach
25
41
26
-
Let's start our journey with a simple C program that infinitely adds two numbers together.
42
+
Let's start our journey by using the text editor of your choice to create a simple C program called `add.c` that infinitely adds two numbers together.
43
+
44
+
{% highlight c %}
45
+
// file: riscv-from-scratch/work/add.c
27
46
28
-
{% highlight bash %}
29
-
cat add.c
30
47
int main() {
31
48
int a = 4;
32
49
int b = 12;
@@ -133,6 +150,7 @@ To figure out what's going on here, we need to take a detour and talk about how
133
150
To answer these questions, let's re-run our GCC command with the `-v` flag to get a more verbose output of what it is actually doing.
134
151
135
152
{% highlight bash %}
153
+
# In the `riscv-from-scratch/work` directory...
136
154
riscv64-unknown-elf-gcc add.c -O0 -g -v
137
155
{% endhighlight %}
138
156
@@ -174,28 +192,25 @@ As we saw above, `gcc` links a default `crt0` unless told to do otherwise. This
174
192
175
193
This may work fine in a general case, but is undoubtedly not going to work for every RISC-V processor. As mentioned previously, one of `crt0`s jobs is to set up the stack, but how can it do that if it doesn't know _where_ the stack should be for the CPU (`-machine`) we're running against? Answer: it can't, at least not without us giving it a bit of assistance.
176
194
177
-
Circling back to the `qemu` command we ran at the beginning of this post (`qemu-system-riscv64 -machine virt -m 128M -gdb tcp::1234 -kernel a.out`), recall we were using the `virt` machine. Fortunately for us, `qemu` exposes a simple way to dump information about a machine in `dtb` (device tree blob) format.
195
+
Circling back to the `qemu` command we ran at the beginning of this post (`qemu-system-riscv64 -machine virt -m 128M -gdb tcp::1234 -kernel a.out`), recall we were using the `virt` machine. Fortunately for us, `qemu` exposes a simple way to dump information about a machine in `dtb` (devicetree blob) format.
178
196
179
197
{% highlight bash %}
180
-
# Go to the ~/usys/riscv folder we created before and create a new dir
181
-
# for our machine information.
182
-
cd ~/usys/riscv && mkdir machines
183
-
cd machines
198
+
# In the `riscv-from-scratch/work` directory...
184
199
185
-
# Use qemu to dump info about the 'virt' machine in dtb (device tree blob)
200
+
# Use qemu to dump info about the 'virt' machine in dtb (devicetree blob)
186
201
# format.
187
202
# The data in this file represents hardware components of a given
Data in `dtb` format is difficult to read considering it's mostly binary, but there is a command-line tool called `dtc` (device tree compiler) that can convert it into something more human-readable.
207
+
Data in `dtb` format is difficult to read considering it's mostly binary, but there is a command-line tool called `dtc` (devicetree compiler) that can convert it into something more human-readable.
193
208
194
209
{% highlight bash %}
195
210
# I'm running MacOS, so I use Homebrew to install this. If you're
196
211
# running another OS you may need to do something else.
197
212
brew install dtc
198
-
# Convert our .dtb into a human-readable .dts (device tree source) file.
213
+
# Convert our .dtb into a human-readable .dts (devicetree source) file.
As you can see, we use the [PROVIDE command](https://access.redhat.com/documentation/en-US/Red_Hat_Enterprise_Linux/4/html/Using_ld_the_GNU_Linker/assignments.html#PROVIDE) to define a symbol called `__stack_top`. `__stack_top` will be accessible from any program linked with this script (assuming the program itself does not also define something named `__stack_top`). We set the value of `__stack_top` to be `ORIGIN(RAM)`, which we know is `0x80000000`, plus `LENGTH(RAM)`, which we know is 128 megabytes (`0x8000000` bytes). This means our `__stack_top` is set to `0x88000000`.
309
322
310
-
For brevity's sake I won't include the entire linker file here, but if you want the final product you can view/download it here: [{{ site.url }}{% link /assets/ld/riscv64-virt.ld %}](/assets/ld/riscv64-virt.ld)
We finally have all we need to create a custom C runtime that works for us, so let's get started. What we need is actually very simple - here is `crt0.s` in its entirety:
326
+
We finally have all we need to create a custom C runtime that works for us, so let's get started. Create a file called `crt0.s` in the `riscv-from-scratch/work/` directory and insert the following:
316
327
317
328
{% highlight nasm %}
318
329
.section .init, "ax"
@@ -427,10 +438,9 @@ Our very last line is an assembler directive, `.end`, which simply marks the end
427
438
428
439
To recap, we've worked through many problems in our quest of debugging a simple C program on a RISC-V processor. We first used `qemu` and `dtc` to find where our memory was located in the `virt` virtual RISC-V machine. We then used this information to take manual control of the memory layout in our customized version of the default `riscv64-unknown-elf-ld` linker script, which then enabled us to accurately define a `__stack_top` symbol. We finished by using this symbol in our own custom `crt0.s` that set up our stack and global pointers and finally called the `main` function. Let's make use of all this work to complete our original goal of debugging our simple C program in GDB.
Copy file name to clipboardExpand all lines: _posts/2019-07-08-riscv-from-scratch-3.markdown
+28-17Lines changed: 28 additions & 17 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -41,23 +41,34 @@ UARTs and USARTs are all around you, even if you may not realize it. They are b
41
41
42
42
### Setup
43
43
44
-
Before we get down to writing our driver, we'll need a few things set up to ensure we can properly compile and link. If you've worked through the previous two posts in this series, you shouldn't have to do anything here, although you may want to make a copy of our linker script and runtime files in the new directory we create.
44
+
Before we get down to writing our driver, we'll need a few things set up to ensure we can properly compile and link. If you've worked through the previous two posts in this series you shouldn't have to do anything here beyond a `cd some/path/to/riscv-from-scratch/work`.
45
45
46
-
In the [previous post]({% post_url 2019-04-27-riscv-from-scratch-2 %}), we customized the default linker script to expose a `__stack_top` symbol and created a minimal C runtime to perform basic initialization tasks, namely setting up the stack and global pointers and calling into `main`. That post details why these things are important, but that information is unnecessary to continue on in this post, so feel free to simply download the necessary files here:
46
+
However, if you missed the previous posts in this series:
47
47
48
-
[crt0.s](/assets/crt0.s)
49
-
50
-
[riscv64-virt.ld](/assets/ld/riscv64-virt.ld)
51
-
52
-
Now that you have downloaded these files (or if you have them from the previous post), move or copy them into a new directory which will serve as the workspace for today's post.
48
+
1. Ensure you have `riscv-qemu` and the RISC-V toolchain installed. You can follow [these instructions](/riscv-from-scratch/2019/03/10/riscv-from-scratch-1.html#qemu-and-risc-v-toolchain-setup) from the first post to complete this.
49
+
2. Clone or fork the [riscv-from-scratch repo](https://github.com/twilco/riscv-from-scratch):
# note: this will overwrite any existing files you may have in `work`
68
+
cp -a src/. work
58
69
{% endhighlight %}
59
70
60
-
You'll also need to have the GNU RISC-V toolchain and QEMU installed to facilitate compilation and emulation. Follow [these instructions](/riscv-from-scratch/2019/03/10/riscv-from-scratch-1.html#qemu-and-risc-v-toolchain-setup) from the first post in this series to complete this.
71
+
If you're curious to know more about this customized linker script and minimal C runtime, check out the [previous post]({% post_url 2019-04-27-riscv-from-scratch-2 %}).
61
72
62
73
### Hardware layout in review
63
74
@@ -114,10 +125,10 @@ This brings us to the last property in our `uart` node, `compatible = "ns16550a"
114
125
115
126
### Creating the basic skeleton of our driver
116
127
117
-
We have all we need to begin writing our driver, so let's begin. Start by ensuring you're in the directory we created before with our C runtime and linker script:
128
+
We have all we need to begin writing our driver, so let's begin. Start by ensuring you're in the `riscv-from-scratch/work`directory we created in the setup section:
118
129
119
130
{% highlight bash %}
120
-
cd ~/projects/riscv-uart
131
+
cd some/path/to/riscv-from-scratch/work
121
132
{% endhighlight %}
122
133
123
134
Now create a file called `ns16550a.s`, which will contain the code for our NS16550A UART driver. In this file, let's start with a basic skeleton containing the functions we want to expose. For now, we'll limit this driver to simply reading and writing chars, or bytes, without worrying about other available capabilities of the NS16550A, such as interrupts.
/Users/twilcock/usys/riscv/riscv64-unknown-elf-gcc-8.2.0-2019.02.0-x86_64-apple-darwin/bin/../lib/gcc/riscv64-unknown-elf/8.2.0/../../../../riscv64-unknown-elf/bin/ld: /var/folders/rg/hbr8vy7d13z9k7pdn0l_n9z51y1g13/T//ccjYQiJc.o: in function `.L0 ':
158
-
/Users/twilcock/projects/riscv-uart/crt0.s:12: undefined reference to `main'
168
+
/Users/twilco/usys/riscv/riscv64-unknown-elf-gcc-8.2.0-2019.02.0-x86_64-apple-darwin/bin/../lib/gcc/riscv64-unknown-elf/8.2.0/../../../../riscv64-unknown-elf/bin/ld: /var/folders/rg/hbr8vy7d13z9k7pdn0l_n9z51y1g13/T//ccjYQiJc.o: in function `.L0 ':
169
+
/Users/twilco/projects/riscv-from-scratch/work/crt0.s:12: undefined reference to `main'
159
170
collect2: error: ld returned 1 exit status
160
171
{% endhighlight %}
161
172
162
173
An utter disaster! Taking a closer look at the error, this line tells us what we need to do:
163
174
164
175
{% highlight bash %}
165
-
/Users/twilcock/projects/riscv-uart/crt0.s:12: undefined reference to `main`
176
+
/Users/twilco/projects/riscv-from-scratch/work/crt0.s:12: undefined reference to `main`
166
177
{% endhighlight %}
167
178
168
179
Taking a look at our `crt0.s` file, we do indeed see a reference to a symbol called `main`:
@@ -184,7 +195,7 @@ _start:
184
195
.end
185
196
{% endhighlight %}
186
197
187
-
This is an easy fix - we simply need to link a file that defines the `main` symbol. We would've wanted to do this anyways at some point, as we need some way to exercise our UART driver, and we can easily do so from `main`. Create a new file called `main.c` in our working directory (`~/projects/riscv-uart`) and define a main function. We'll also call `uart_put_char` to ensure that `main` is able to find our definition of it in `ns16550a.s`.
198
+
This is an easy fix - we simply need to link a file that defines the `main` symbol. We would've wanted to do this anyways at some point, as we need some way to exercise our UART driver, and we can easily do so from `main`. Create a new file called `main.c` in our working directory (`riscv-from-scratch/work`) and define a main function. We'll also call `uart_put_char` to ensure that `main` is able to find our definition of it in `ns16550a.s`.
0 commit comments