WEBVTT

00:00:00.570 --> 00:00:05.880
Another service provided by operating system
is dealing properly with the attempt to execute

00:00:05.880 --> 00:00:09.420
instructions with "illegal" opcodes.

00:00:09.420 --> 00:00:13.740
Illegal is quotes because that just means
opcodes whose operations aren't implemented

00:00:13.740 --> 00:00:15.089
directly by the hardware.

00:00:15.089 --> 00:00:22.390
As we'll see, it's possible extend the functionality
of the hardware via software emulation.

00:00:22.390 --> 00:00:26.789
The action of the CPU upon encountering an
illegal instruction (sometimes referred to

00:00:26.789 --> 00:00:34.590
as an unimplemented user operation or UUO)
is very similar to how it processes interrupts.

00:00:34.590 --> 00:00:39.370
Think of illegal instructions as an interrupt
caused directly by the CPU!

00:00:39.370 --> 00:00:45.270
As for interrupts, the execution of the current
instruction is suspended and the control signals

00:00:45.270 --> 00:00:53.289
are set to values to capture PC+4 in the XP
register and set the PC to, in this case,

00:00:53.289 --> 00:00:55.500
0x80000004.

00:00:55.500 --> 00:01:02.470
Note that bit 31 of the new PC, aka the supervisor
bit, is set to 1, meaning that the OS handler

00:01:02.470 --> 00:01:06.200
will have access to the kernel-mode context.

00:01:06.200 --> 00:01:09.630
Here's some code similar to that found in
the Tiny Operating System (TinyOS), which

00:01:09.630 --> 00:01:12.240
you'll be experimenting with in the final
lab assignment.

00:01:12.240 --> 00:01:17.829
Let's do a quick walk-through of the code
executed when an illegal instruction is executed.

00:01:17.829 --> 00:01:21.720
Starting at location 0, we see the branches
to the handlers for the various interrupts

00:01:21.720 --> 00:01:22.799
and exceptions.

00:01:22.799 --> 00:01:31.340
In the case of an illegal instruction, the
BR(I_IllOp) in location 4 will be executed.

00:01:31.340 --> 00:01:35.430
Immediately following is where the OS data
structures are allocated.

00:01:35.430 --> 00:01:40.979
This includes space for the OS stack, UserMState
where user-mode register values are stored

00:01:40.979 --> 00:01:46.479
during interrupts, and the process table,
providing long-term storage for the complete

00:01:46.479 --> 00:01:51.158
state of each process while another process
is executing.

00:01:51.158 --> 00:01:55.969
When writing in assembly language, it's convenient
to define macros for operations that are used

00:01:55.969 --> 00:01:57.590
repeatedly.

00:01:57.590 --> 00:02:02.500
We can use a macro call whenever we want to
perform the action and the assembler will

00:02:02.500 --> 00:02:07.700
insert the body of the macro in place of the
macro call, performing a lexical substitution

00:02:07.700 --> 00:02:10.479
of the macro's arguments.

00:02:10.479 --> 00:02:14.980
Here's a macro for a two-instruction sequence
that extracts a particular field of bits from

00:02:14.980 --> 00:02:16.750
a 32-bit value.

00:02:16.750 --> 00:02:22.590
M is the bit number of the left-most bit,
N is the bit number of the right-most bit.

00:02:22.590 --> 00:02:28.290
Bits are numbered 0 through 31, where bit
31 is the most-significant bit, i.e., the

00:02:28.290 --> 00:02:33.110
one at the left end of the 32-bit binary value.

00:02:33.110 --> 00:02:37.750
And here are some macros that expand into
instruction sequences that save and restore

00:02:37.750 --> 00:02:43.800
the CPU registers to or from the UserMState
temporary storage area.

00:02:43.800 --> 00:02:48.560
With those macros in hand, let's see how illegal
opcodes are handled.

00:02:48.560 --> 00:02:53.110
Like all interrupt handlers, the first action
is to save the user-mode registers in the

00:02:53.110 --> 00:02:57.530
temporary storage area and initialize the
OS stack.

00:02:57.530 --> 00:03:02.260
Next, we fetch the illegal instruction from
the user-mode program.

00:03:02.260 --> 00:03:07.710
Note that the saved PC+4 value is a virtual
address in the context of the interrupted

00:03:07.710 --> 00:03:08.950
program.

00:03:08.950 --> 00:03:14.090
So we'll need to use the MMU routines to compute
the correct physical address - more about

00:03:14.090 --> 00:03:16.910
this on the next slide.

00:03:16.910 --> 00:03:23.380
Then we'll use the opcode of the illegal instruction
as an index into a table of subroutine addresses,

00:03:23.380 --> 00:03:27.950
one for each of the 64 possible opcodes.

00:03:27.950 --> 00:03:33.061
Once we have the address of the handler for
this particular illegal opcode, we JMP there

00:03:33.061 --> 00:03:36.230
to deal with the situation.

00:03:36.230 --> 00:03:41.730
Selecting a destination from a table of addresses
is called "dispatching" and the table is called

00:03:41.730 --> 00:03:44.020
the "dispatch table".

00:03:44.020 --> 00:03:48.800
If the dispatch table contains many different
entries, dispatching is much more efficient

00:03:48.800 --> 00:03:53.340
in time and space than a long series of compares
and branches.

00:03:53.340 --> 00:03:58.340
In this case, the table is indicating that
the handler for most illegal opcodes is the

00:03:58.340 --> 00:04:03.680
UUOError routine,
so it might have smaller and faster simply

00:04:03.680 --> 00:04:09.910
to test for the two illegal opcodes the OS
is going to emulate.

00:04:09.910 --> 00:04:15.200
Illegal opcode 1 will be used to implement
procedure calls from user-mode to the OS,

00:04:15.200 --> 00:04:17.779
which we call supervisor calls.

00:04:17.779 --> 00:04:20.899
More on this in the next segment.

00:04:20.899 --> 00:04:26.120
As an example of having the OS emulate an
instruction, we'll use illegal opcode 2 as

00:04:26.120 --> 00:04:30.400
the opcode for the SWAPREG instruction, which
we'll discuss now.

00:04:30.400 --> 00:04:35.340
But first just a quick look at how the OS
converts user-mode virtual addresses into

00:04:35.340 --> 00:04:38.190
physical addresses it can use.

00:04:38.190 --> 00:04:44.030
We'll build on the MMU VtoP procedure, described
in the previous lecture.

00:04:44.030 --> 00:04:48.630
This procedure expects as its arguments the
virtual page number and offset fields of the

00:04:48.630 --> 00:04:53.340
virtual address, so,
following our convention for passing arguments

00:04:53.340 --> 00:04:58.210
to C procedures, these are pushed onto the
stack in reverse order.

00:04:58.210 --> 00:05:02.820
The corresponding physical address is returned
in R0.

00:05:02.820 --> 00:05:08.250
We can then use the calculated physical address
to read the desired location from physical

00:05:08.250 --> 00:05:09.250
memory.

00:05:09.250 --> 00:05:12.970
Okay, back to dealing with illegal opcodes.

00:05:12.970 --> 00:05:15.750
Here's the handler for opcodes that are truly
illegal.

00:05:15.750 --> 00:05:20.440
In this case the OS uses various kernel routines
to print out a helpful error message on the

00:05:20.440 --> 00:05:23.970
user's console, then crashes the system!

00:05:23.970 --> 00:05:28.530
You may have seen these "blue screens of death"
if you run the Windows operating system, full

00:05:28.530 --> 00:05:30.830
of cryptic hex numbers.

00:05:30.830 --> 00:05:35.620
Actually, this wouldn't be the best approach
to handling an illegal opcode in a user's

00:05:35.620 --> 00:05:36.620
program.

00:05:36.620 --> 00:05:41.400
In a real operating system, it would be better
to save the state of the process in a special

00:05:41.400 --> 00:05:45.610
debugging file historically referred to as
a "core dump"

00:05:45.610 --> 00:05:50.580
and then terminate this particular process,
perhaps printing a short error message on

00:05:50.580 --> 00:05:53.830
the user's console to let them know what happened.

00:05:53.830 --> 00:05:58.990
Then later the user could start a debugging
program to examine the dump file to see where

00:05:58.990 --> 00:06:00.490
their bug is.

00:06:00.490 --> 00:06:06.020
Finally, here's the handler that will emulate
the actions of the SWAPREG instruction, after

00:06:06.020 --> 00:06:12.840
which program execution will resume as if
the instruction had been implemented in hardware.

00:06:12.840 --> 00:06:18.280
SWAPREG is an instruction that swaps the values
in the two specified registers.

00:06:18.280 --> 00:06:24.610
To define a new instruction, we'd first have
to let the assembler know to convert the swapreg(ra,rc)

00:06:24.610 --> 00:06:27.580
assembly language statement into binary.

00:06:27.580 --> 00:06:33.020
In this case we'll use a binary format similar
to the ADDC instruction, but setting the unused

00:06:33.020 --> 00:06:35.090
literal field to 0.

00:06:35.090 --> 00:06:40.830
The encoding for the RA and RC registers occur
in their usual fields and the opcode field

00:06:40.830 --> 00:06:44.260
is set to 2.

00:06:44.260 --> 00:06:46.490
Emulation is surprisingly simple.

00:06:46.490 --> 00:06:51.770
First we extract the RA and RC fields from
the binary for the swapreg instruction and

00:06:51.770 --> 00:06:57.180
convert those values into the appropriate
byte offsets for accessing the temporary array

00:06:57.180 --> 00:06:59.530
of saved register values.

00:06:59.530 --> 00:07:04.810
Then we use RA and RC offsets to access the
user-mode register values that have been saved

00:07:04.810 --> 00:07:07.040
in UserMState.

00:07:07.040 --> 00:07:12.270
We'll make the appropriate interchange, leaving
the updated register values in UserMState,

00:07:12.270 --> 00:07:17.240
where they'll be loaded into the CPU registers
upon returning from the illegal instruction

00:07:17.240 --> 00:07:19.030
interrupt handler.

00:07:19.030 --> 00:07:25.870
Finally, we'll branch to the kernel code that
restores the process state and resumes execution.

00:07:25.870 --> 00:07:28.120
We'll see this code in the next segment.