mirror of
https://github.com/pkivolowitz/asm_book.git
synced 2026-06-21 01:56:47 +08:00
added a first project type project plus other changes
This commit is contained in:
parent
1c15934889
commit
1f1a730d9e
3 changed files with 242 additions and 24 deletions
|
|
@ -300,9 +300,16 @@ What would a book about assembly language be without bit bashing?
|
|||
challenge to your growing mastery. Here are very brief descriptions
|
||||
presented in alphabetical order.
|
||||
|
||||
---
|
||||
|
||||
Perhaps before you tackle these, check out the fully described
|
||||
[FIZZBUZZ](./section_1/fizzbuzz/README.md) program first.
|
||||
|
||||
---
|
||||
|
||||
Then try [this](./projects/first_project/README.md) as your very first
|
||||
project. With some blank lines and comments it weighs in at 35 lines.
|
||||
|
||||
---
|
||||
The [DIRENT](./projects/DIRENT/README.md) project demonstrates how a
|
||||
complex `struct` can be used in assembly language.
|
||||
|
|
|
|||
|
|
@ -1,6 +1,10 @@
|
|||
# Reading Directories
|
||||
|
||||
In this project you will write your first assembly language program from scratch. The program will use library calls to open the given Linux directory (or the current directory if no command line argument is given) for reading. It will read every directory entry, printing out each file's inode number, type and name.
|
||||
In this project you will write your non-trivial first assembly language
|
||||
program from scratch. The program will use library calls to open the
|
||||
given Linux directory (or the current directory if no command line
|
||||
argument is given) for reading. It will read every directory entry,
|
||||
printing out each file's inode number, type and name.
|
||||
|
||||
## Samples
|
||||
|
||||
|
|
@ -19,7 +23,11 @@ perryk@ROCI pk_dirent % ./a.out
|
|||
perryk@ROCI pk_dirent %
|
||||
```
|
||||
|
||||
The first column is the named file's inode number. Think of an inode number as a file's unique (per file system) serial number. You must print it left justified in a field of 20 digits using `printf` - since it is possible you've never used `printf` before, I will supply the correct (for `C`) statement:
|
||||
The first column is the named file's inode number. Think of an inode
|
||||
number as a file's unique (per file system) serial number. You must
|
||||
print it left justified in a field of 20 digits using `printf` - since
|
||||
it is possible you've never used `printf` before, I will supply the
|
||||
correct (for `C`) statement:
|
||||
|
||||
```c
|
||||
printf("%-20llu 0x%02x %s\n", de->d_ino, de->d_type, de->d_name);
|
||||
|
|
@ -31,7 +39,8 @@ In my code, `de` is defined:
|
|||
struct dirent * de;
|
||||
```
|
||||
|
||||
The second column is the file's type printed as a single byte's worth of hex.
|
||||
The second column is the file's type printed as a single byte's worth of
|
||||
hex.
|
||||
|
||||
The third column is the file's name.
|
||||
|
||||
|
|
@ -86,39 +95,55 @@ man closedir
|
|||
man readdir
|
||||
```
|
||||
|
||||
`man` is your friend, though of course in the 21st century it should be called `person`. To learn more about `man`, do the obvious thing:
|
||||
`man` is your friend, though of course in the 21st century it should be
|
||||
called `person`. To learn more about `man`, do the obvious thing:
|
||||
|
||||
```text
|
||||
man man
|
||||
```
|
||||
|
||||
"Just" 439 lines.
|
||||
|
||||
**DON'T DO THIS FROM A MAC TERMINAL -- WHY? STEVE JOBS THAT'S WHY.**
|
||||
|
||||
It will be equally pointless to try the above Linux shell commands from a Windows command prompt but hey - give it a try. So where should you read these `man` pages? In your ARM Linux VM, of course.
|
||||
It will be equally pointless to try the above Linux shell commands from
|
||||
a Windows command prompt but hey - give it a try. So where should you
|
||||
read these `man` pages? In your ARM Linux VM, of course.
|
||||
|
||||
The reason to not read the `man` pages on the Mac is that everything beyond the name of the functions will be different. You know, "Think Different."
|
||||
The reason to not read the `man` pages on the Mac is that everything
|
||||
beyond the name of the functions will be different. You know, "Think
|
||||
Different."
|
||||
|
||||
## `opendir()`
|
||||
|
||||
This function takes a NULL terminated `C-string` and attempts to open it as a directory. Get the details from the `man` page. If you get an error return, pass the attempted directory name to `perror()` to get the right error message.
|
||||
This function takes a NULL terminated `C-string` and attempts to open it
|
||||
as a directory. Get the details from the `man` page. If you get an error
|
||||
return, pass the attempted directory name to `perror()` to get the right
|
||||
error message.
|
||||
|
||||
## `closedir()`
|
||||
|
||||
Call this function to close a successfully opened directory. Get the details from the `man` page.
|
||||
Call this function to close a successfully opened directory. Get the
|
||||
details from the `man` page.
|
||||
|
||||
## `readdir()`
|
||||
|
||||
Call this function to be given a pointer to the next `dirent` or `NULL` if there are no more (or there is an error). Pay attention to the `man` page to distinguish between no more `dirent` structures and an error. In short, `errno` should be initialized to 0 then checked once you've gotten a `NULL` back from `readdir()`.
|
||||
Call this function to be given a pointer to the next `dirent` or `NULL`
|
||||
if there are no more (or there is an error). Pay attention to the `man`
|
||||
page to distinguish between no more `dirent` structures and an error. In
|
||||
short, `errno` should be initialized to 0 then checked once you've
|
||||
gotten a `NULL` back from `readdir()`.
|
||||
|
||||
## Source code to a `C` version
|
||||
|
||||
At the beginning of this document I said:
|
||||
|
||||
```text
|
||||
In this project you will write your first assembly language program from scratch.
|
||||
```
|
||||
*In this project you will write your first assembly language program
|
||||
from scratch.*
|
||||
|
||||
but here's the source code to my `C` version because you may be just getting started with `C` and Linux programming.
|
||||
but here's the source code to my `C` version because you may be just
|
||||
getting started with `C` and Linux programming. And because I'm a
|
||||
wonderful pushover of a professor.
|
||||
|
||||
```c
|
||||
#include <stdio.h> /* 1 */
|
||||
|
|
@ -151,33 +176,42 @@ int main(int argc, char ** argv) {
|
|||
|
||||
### Line 6 and Line 21
|
||||
|
||||
Command line programs return 0 to who called them when all is well. A non-zero return value signifies and error.
|
||||
Command line programs return 0 to who called them when all is well. A
|
||||
non-zero return value signifies and error.
|
||||
|
||||
### Lines 7, 9 and 10
|
||||
|
||||
Notice how the program is made to default to the current directory (`"."`) which can be overridden if a command line argument is supplied.
|
||||
Notice how the program is made to default to the current directory
|
||||
(`"."`) which can be overridden if a command line argument is supplied.
|
||||
|
||||
### Line 15
|
||||
|
||||
`errno` is initialized to 0 and then quizzed to see if it turned non-zero when `readdir()` finally returns `NULL`.
|
||||
`errno` is initialized to 0 and then quizzed to see if it turned
|
||||
non-zero when `readdir()` finally returns `NULL`.
|
||||
|
||||
### Line 17
|
||||
|
||||
Implementing this line is where you will need to calculate the correct offsets to each data member.
|
||||
Implementing this line is where you will need to calculate the correct
|
||||
offsets to each data member.
|
||||
|
||||
See the book chapter on `struct`.
|
||||
|
||||
### Line 18
|
||||
|
||||
The error condition is distinguished from the end of the directory by looking at `errno`.
|
||||
The error condition is distinguished from the end of the directory by
|
||||
looking at `errno`.
|
||||
|
||||
## Getting the address of `errno`
|
||||
|
||||
`errno` is an `extern`. To store anything into it (or query its contents), you must have its address. For reasons which will be explained, getting its address is accomplished by calling a [library function](http://refspecs.linux-foundation.org/LSB_4.0.0/LSB-Core-generic/LSB-Core-generic/baselib---errno-location.html).
|
||||
`errno` is an `extern`. To store anything into it (or query its
|
||||
contents), you must have its address. For reasons which will be
|
||||
explained, getting its address is accomplished by calling a [library
|
||||
function](http://refspecs.linux-foundation.org/LSB_4.0.0/LSB-Core-generic/LSB-Core-generic/baselib---errno-location.html).
|
||||
|
||||
## Remember to properly set the return value of `main()`
|
||||
|
||||
If all ends well, zero should be returned from `main()`. If any error is found, a value of 1 should be returned.
|
||||
If all ends well, zero should be returned from `main()`. If any error is
|
||||
found, a value of 1 should be returned.
|
||||
|
||||
Check in this way:
|
||||
|
||||
|
|
@ -202,14 +236,19 @@ pk_dirent > echo $?
|
|||
pk_dirent >
|
||||
```
|
||||
|
||||
`$?` is a shell variable that contains the value returned from the last program run by the shell.
|
||||
`$?` is a shell variable that contains the value returned from the last
|
||||
program run by the shell.
|
||||
|
||||
## Likely source of error
|
||||
|
||||
If you're printing garbage, double check your calculations of offsets within the `dirent`. While this isn't the only explanation, it is a likely explanation.
|
||||
If you're printing garbage, double check your calculations of offsets
|
||||
within the `dirent`. While this isn't the only explanation, it is a
|
||||
likely explanation.
|
||||
|
||||
## Setting expectations
|
||||
|
||||
I provide the following not as a challenge, but to set your expectations.
|
||||
I provide the following not as a challenge, but to set your
|
||||
expectations.
|
||||
|
||||
My assembly language solution is about 60 lines plus comments. If you find yourself writing much more than this, you're doing it wrong.
|
||||
My assembly language solution is about 60 lines plus comments. If you
|
||||
find yourself writing much more than this, you're doing it wrong.
|
||||
|
|
|
|||
172
projects/first_project/README.md
Normal file
172
projects/first_project/README.md
Normal file
|
|
@ -0,0 +1,172 @@
|
|||
# Echo stdin to stdout
|
||||
|
||||
This is your first assembly language project. The program, befitting a
|
||||
first project, is straight forward. In a loop, it reads 1 byte from
|
||||
`stdin` and writes it to `stdout`. See below for how the program is made
|
||||
to exit.
|
||||
|
||||
## Using man pages
|
||||
|
||||
Below you will find references using and reading *man* pages. This is
|
||||
how the early Unix system supplied their documentation and this is still
|
||||
used.
|
||||
|
||||
*Warning: don't use man from the Macintosh terminal!*
|
||||
|
||||
Apple changed some function signatures and meanings. Use man only from
|
||||
the WSL or Linux command line to be certain you're getting the right
|
||||
information.
|
||||
|
||||
## Invocation
|
||||
|
||||
### From the keyboard
|
||||
|
||||
If you simply run the program, it will read your keystrokes. When
|
||||
you hit enter, the line you typed will be written back to you.
|
||||
Execution stops when you:
|
||||
|
||||
a) hit ^c (control C) - this sends a signal to the receiving process
|
||||
which usually kills it.
|
||||
|
||||
b) you enter an end-of-file - ^d (control D) on Linux.
|
||||
|
||||
Sample:
|
||||
|
||||
```text
|
||||
a@PROMETHEUS:~/repos/pk_echo$ ./a.out
|
||||
Foo
|
||||
Foo
|
||||
a@PROMETHEUS:~/repos/pk_echo$
|
||||
```
|
||||
|
||||
I invoked the program and typed "Foo" and hit enter. The program wrote
|
||||
back "Foo" and a new line. Then I hit ^d so the program exited.
|
||||
|
||||
### From redirection
|
||||
|
||||
An awsome feature of the command line is the ability to redirect input
|
||||
and output. Since the program simply reads from `stdin`, and `stdin` can
|
||||
be redirected, you get this feature for free:
|
||||
|
||||
```text
|
||||
a@PROMETHEUS:~/repos/pk_echo$ ./a.out < README.md
|
||||
# pk_echo
|
||||
A COMPORG project suitable for 1st project
|
||||
a@PROMETHEUS:~/repos/pk_echo$
|
||||
```
|
||||
|
||||
## `stdin` and `stdout`
|
||||
|
||||
You know what `cin` and `cout` are. They are built on top of `stdin` and
|
||||
`stdout`. These in turn devolve down into input and output channels
|
||||
denoted by *file descriptors*. See next:
|
||||
|
||||
| File Descriptor | Name | C++ Equivalence |
|
||||
| - | - | - |
|
||||
| 0 | stdin | cin |
|
||||
| 1 | stdout | cout |
|
||||
| 2 | stderr | cerr |
|
||||
|
||||
## `read()` and `write()`
|
||||
|
||||
The lowest level I/O on a Linux system are `read()` and `write()`. Here
|
||||
are their signatures:
|
||||
|
||||
```c
|
||||
ssize_t read(int fd, void *buf, size_t count);
|
||||
ssize_t write(int fd, const void *buf, size_t count);
|
||||
```
|
||||
|
||||
First, let's examine the types and their correspondence to register
|
||||
types.
|
||||
|
||||
| Type | Equivalence | Register Type |
|
||||
| - | - | - |
|
||||
| ssize_t | long | x |
|
||||
| int | int | w |
|
||||
| void * | void * | x |
|
||||
| size_t | unsigned long | x |
|
||||
|
||||
You are expected to use the correct register types.
|
||||
|
||||
To get detailed information on these two calls, use the `man` pages.
|
||||
|
||||
`man 2 read`
|
||||
|
||||
`man 2 write`
|
||||
|
||||
From this documentation you can determine the meaning of what they
|
||||
return. The return value of `read()`, if not 1, should cause your
|
||||
program to gracefully exit. A negative return value from `write()`
|
||||
should cause an error message to be printed using `perror()` followed by
|
||||
a graceful exit.
|
||||
|
||||
## `perror()`
|
||||
|
||||
There is an externally defined integer variable called `errno` that
|
||||
receives error codes from various functions including those in the
|
||||
C runtime library (CRTL). Reading the documentation indicated above
|
||||
will mention `errno`.
|
||||
|
||||
The CRTL provides the function `perror()` to `p`rint `error`s.
|
||||
`perror()` interprets `errno` to generate an error message for you.
|
||||
This is the signature of `perror()`:
|
||||
|
||||
```c
|
||||
void perror(const char *s);
|
||||
```
|
||||
|
||||
The argument `s` points to a null terminated C string that is prepended
|
||||
to the error message.
|
||||
|
||||
The man page can be accessed here:
|
||||
|
||||
`man 3 perror`
|
||||
|
||||
## `read()` needs a buffer
|
||||
|
||||
The second argument to both `read()` and `write()` is a `void *`. This
|
||||
is a generic pointer - it points to a location in memory that's
|
||||
considered just bytes.
|
||||
|
||||
In your `data` segment, I suggest declaring a fixed buffer. There's no
|
||||
reason not to make it 8 bytes long knowing that you will only ever fill
|
||||
one byte.
|
||||
|
||||
## Special registers
|
||||
|
||||
Remember to begin your program with:
|
||||
|
||||
```text
|
||||
stp x29, x30, [sp, -16]!
|
||||
mov x29, sp
|
||||
```
|
||||
|
||||
and end it with:
|
||||
|
||||
```text
|
||||
ldp x29, x30, [sp], 16
|
||||
mov w0, wzr
|
||||
ret
|
||||
```
|
||||
|
||||
## Other reminders
|
||||
|
||||
* Remember to have a `main` marked as global.
|
||||
|
||||
* Remember to `.align`.
|
||||
|
||||
* Remember to have a `.text` segment
|
||||
|
||||
* Remember to end your program with `.end`
|
||||
|
||||
## Work rules
|
||||
|
||||
All work is done alone. No partners.
|
||||
|
||||
## Expectations
|
||||
|
||||
The following is provided to set your expectations and is not a
|
||||
challenge. Including some comments and blank lines, my solution runs
|
||||
35 lines. You have one week to write about 35 lines plus one grace day.
|
||||
You should be able to crush this if you ask questions and read my book.
|
||||
Loading…
Reference in a new issue