In my last post, I published my solution for the first exercise in the xv6 book for 6.828’s 2019 version. Unfortunately, I somehow found it hard getting familiar with RISC-V instructions and, at the same time, trying to understand all these O/S concepts. Moreover, since it’s new, I can’t find much information to reference on the Internet when I run into issues like environment settings. So, I’ve decided to switch back to it’s 2018 version.
So, first things first, as recommended in Exercise 1 of Lab 1: C, Assembly, Tools, and Bootstrapping, I read the first two chapters of PC Assembly Language by Paul A. Carter. At the time of writing, the book is publicly available at here.
This post (series?) is not intended to be a comprehensive walkthrough of the book, but merely a summary of knowledge points that I found new / interesting / useful to me.
Chapter 1: Introduction
- Memory is measured in units of kilobytes (2^10 bytes), megabytes (2^20 bytes) and gigabytes (2^30 bytes).
- 16 bytes is a paragraph.
- ASCII only uses the lower 7-bits.
1.2.3 The 80×86 Family of CPUs
- 8088, 8086
- Provide 16-bit registers: AX, BX, CX, DX, SI, DI, BP, SP, CS, DS, SS, ES, IP, FLAGS.
- PF is the parity flag which indicates the odd or evenness of a result.
- Only support up to one megabyte of memory.
- Only operate in real mode. In real mode, one program can access any memory address, including the memory of other programs.
- Program memory has to be divided into segments. Each segment can not be larger than 64K.
- Provide 16-bit registers: AX, BX, CX, DX, SI, DI, BP, SP, CS, DS, SS, ES, IP, FLAGS.
- 80286
- Introduces 16-bit protected mode. In 16-bit protected mode, it can access up to 16 megabytes and protect programs from accessing each other’s memory.
- Program memory still has to be divided into segments. Each segment still can not be larger than 64K.
- 80386
- Extends many registers, EAX, EBX, ECX, EDX, ESI, EDI, EBP, ESP, EIP to hold 32-bits and adds two new 16-bit registers FS and GS.
- Introduces 32-bit protected mode. In 32-bit protected mode, it can access up to 4 gigabytes.
- Program memory still has to be divided into segments, but now each segment can also be up to 4 gigabytes in size.
- 80486 / Pentium / Pentium Pro
- Speed up the execution of instructions.
- Pentium MMX
- Adds the MMX (MultiMedia eXentions) instructions to the Pentium. These instructions speed up common graphics operations.
- Pentium II
- Adds MMX instructions to Pentium Pro.
- Pentium III
- Faster Pentium II.
1.2.4 8086 16-bit Registers
- Changing AX‘s value will change AH and AL and vise versa.
- SI and DI are often used as pointers but can be used for many of the same purposes as the general registers. Cannot be decomposed into 8-bit registers.
- Segment registers: CS (Code Segment), DS (Data Segment), SS (Stack Segment) and ES (Extra Segment).
- IP is used with CS to keep track of the address of the next instruction to be executed by the CPU.
- FLAGS stores information about the result of a previous instruction.
- Not all instructions modify the bits in FLAGS.
1.2.5 80386 32-bit Registers
- There is no way to access the upper 16-bits of EAX directly.
- Segments registers are still 16-bit in the 80386.
- FS and GS are extra temporary segment registers like ES. Their names do not stand for anything.
1.2.6 Real Mode
- In real mode, BIOS requires some of the limited 1M of memory for its code and for h/w devices like the video screen.
- While 20 address lines are available for the 8086, registers only have 16-bits. In order to represent the entire address space, it has to use more than one registers. That is where segment registers come into play.
- 8086 uses two 16-bit values to determine an address. The first 16-bit value is called the selector, which must be stored in segment registers. The second is called the offset. The physical address referenced by the 32-bit selector:offset pair is computed by:
16 * selector + offset
. - Disadvantages:
- When execution moves from one 64K segment to another, the value of CS must be changed.
- Segmented addresses collide with each others. 04808 can be referenced by 047C:0048 and 047D:0038.
1.2.7 16-bit Protected Mode
- In protected mode, a selector value is an index into a descriptor table. Each segment is assigned an entry in a descriptor table. The index of the entry of the segment is stored in segment registers as the selector value. This entry contains all the info that the system needs to know about the segment, including:
- Is it currently in memory?
- If in memory, where is it?
- Access permissions (e.g., read-only)
- Protected mode uses virtual memory. Segments are moved between memory and disk. Currently unused code and data are stored temporarily on disk until they are needed again. All of this is done transparently by the O/S.
- Disadvantage:
- Registers are still 16-bits, which means segments still cannot be larger than 64K.
1.2.8 32-bit Protected Mode
- Offsets are now 32-bits! Banzai! Segments can have sizes up to 4 gigabytes.
- Virtual memory system works with 4K-sized pages, instead of segments.
- In 286 16-bit mode, either the entire segment is in memory or none of it is. As our segments get much larger, this method is no longer practical.
1.2.9 Interrputs
- Interrupts cause control to be passed to an interrupt handler.
- At the beginning of physical memory, a table of interrupt vectors resides that contain the segmented addresses of the interrupt handlers.
- Error interrupts are also called traps. Traps generally do not return.
- Interrupts generated from the interrupt instruction are called software interrupts.
1.3.5 Directives
- NASM’s preprocessor directives start with
%
instead of a#
as in C. - The
equ
directive can be used to define a symbol:symbol equ value
- Symbol values can not be redefined later. Macros can.
- RESX directives only define room for data; DX directives also initiate them.
- Double and single quotes are treated the same.
- TIMES directives is often useful for large sequences:
times 100 db 0
- The assembler does not keep track of the type of data that a label refers to.
1.3.6 Input and Output
- Assembly languages provide no standard libraries. They must either directly access hardware (which is a privileged operation in protected mode) or use whatever low level routines that the O/S provides.
1.4.1 First Program
- For DOS / Windows, all C symbols (i.e., functions and global variables) have a underscore prefix appended to them by the C compiler. The Linux C compiler does not prepend anything to C symbol names.
- The global directive gives the specified label (or labels) external scope.
1.4.6 Understanding an Assembly Listing File
- Each module may define its own labels in the data segment (and the other segments too). In the link step, all these segment label definitions are combined to form one data segment.
- Before the link step, often the complete code for an instruction can not be computed yet:
mov eax, [input1]
, in the listing file, is displayed as0000002C A1[00000000]
- IBM mainframes, most RISC processors and Motorola processors all use big endian.
Chapter 2: Basic Assembly Language
2.1.1 Integer Representation
- Signed magnitude
+127
:01111111
-127
:11111111
- Two possible values of zero, complicates the logic for the CPU.
- One’s complement
+127
:01111111
-127
:10000000
- Two possible values of zero, complicates the logic for the CPU.
- Two’s complement
+127
:01111111
-127
:10000001
- Advantage: The rules for addition and subtraction are exactly the same as for unsigned integers.
2.1.2 Sign Extension
- CBW (Convert Byte to Word)
- CWD (Convert Word to Double word)
- CWDE (Convert Word to Double word Extended)
- CDQ (Convert Double word to Quad word)
- Consult page 31 of this book, or the Intel developer manual, if you really need to understand what these instruction do…
- Same rules apply to other instructions including MUL, IMUL, DIV, IDIV, ADC, SBB…
- ANSI C does not define whether the char type is signed or not. It is up to the compiler to decide this.
char ch; while ((ch = fgetc(fp) != EOF)) { /* do something with ch */ }
- Why is this buggy code? EOF is an integer, so how would you distinguish EOF from a byte whose value is really 0xFF?
- Solution: declare ch as int type, then safely truncate it inside the loop.
2.2.2 Branch Instructions
- SHORT
- Can only move up or down 128 bytes in memory.
- Uses a single byte to store the displacement of the jump.
SHORT jmp
- NEAR
- Any location in a segment.
- 80386 supports both 2-byte and 4-byte near jumps.
WORD jmp
to use the 2-byte version.
- FAR
- Allows control to move to another code segment.
- JXX instruction family naming trivia:
- Signed: L, G (Lower, Greater)
- Unsigned: B, A (Below, Above)
This completes the first 2 chapters of the reading memo. 2.4 Example: Finding Prime Numbers illustrates a good assembly program example. I hesitated whether I should try to write one myself as an exercise, but didn’t have the time.