PowerPC assembly language beginners guide.
Chapter 2 - A few instructions, variables, toc and bss
The first thing we need to be able to do is get data into the processor; if we can't do this, then we can't do any work. The basic instruction used for loading data from memory is, funnily enough, called "Load". There are a few variants on it, depending on the size of the data we wish to load. The most frequently used, loads a word from memory into one of the processors registers. The full name of this instruction is Load Word and Zero, or lwz as Fantasm knows it. lwz takes the form of:
Where rx is an integer register, such as r3 and EA describes the effective address of where to get the data from. The data will be loaded into rx affecting all bits of the register, as on most PowerPC processors the integer registers are 32 bits wide (there are 64 bit PowerPC processors such as the 620. If an LWZ instruction is processed on one of these, the upper 32 bits are set to zero, hence Load Word and zero).
lwz is complimentary to STore Word, which takes data (32 bits) from a register and stores it in memory.
Registers can be loaded with data quite easily with the Load Immediate instruction; LI. This takes a 16 bit value and puts it into the lower 16 bits of a register. Because the register is 32 bits wide, the 16 bit value is sign extended to affect the upper 16 bits. This means that if bit 15 is a 1, bits 16 to 31 are made 1's. If bit 15 is a zero, bits 16 to 31 are made zero's. This Load Immediate loads a 16 bit signed number into a register
Suppose we want to set a 32 bit variable called my_variable to 0x00001234, here's what the code would look like:
li r3,0x1234 stw r3,my_variable(`bss)
You're probably thinking "what's this bss thingy?" Ok. don't panic, it's not hard, we'll cover it in a second under "variables". Notice in the above example I cheated by loading in a 16 bit value that would not have bit 15 set, so 0x1234 comes out just as 0x00001234 when loaded into the register. If I had loaded 0xf234 the register would have been sign extended to become 0xfffff234, which may not be what we wanted. If we wanted to load a 32 bit unsigned value into a register with Fantasm 5 we could use a "macro" called "movei". Macros, at the simplest level are a way of defining new instructions to do things you couldn't normally do. movei is used exactly the same as li:
After this macro, r3 would be equal to 0x0000f234. Hopefully you can see the difference. To use this macro in Fantasm 5 you need to either make LS_PPC_macros.def a globally included file (these are files that are inserted into every file in your project) or include the file into your file with an "includeh" directive. More about directives later.
Note, we get a little complicated here for a while. No need to understand this section completely just now, I just want to explain some things so that when you see them in the examples, you'll have at least seen them before.
In a high level language, sometimes you need to define variables before using them, and sometimes you don't. In assembly language you do need to define your variables. Generally we can use two kinds of variables - global or local. Global variables are accessible to all parts of your program, whilst local variables are only accessible to the routine or function (a part of a program that can be called many times over from other parts of the program) that they are defined in.
Global variables are stored in a section of RAM called the BSS. Nobody we've ever met can tell us why it's called the BSS - it just is. (Update April 2000 - OK, after a few years and many people saying the same thing I thing I can quite safely state that BSS stands for Block Started by Symbol. I bet you're happier knowing that!). When Fantasm is building your program, it calculates the size of the BSS and stores it in the fragment. When the Mac loads the fragment, it sets aside an area of memory big enough to hold all your global variables and then stores the address of this area in the first entry in the fragment's Table Of Contents or TOC. If we then copy this address into a register, it can be used as a pointer to the global variables. We can then access (read from and write to) our global variables as offsets (using labels) from this register. The start up macro performs this function for us (along with another important function of saving all the registers before we start messing with them), and puts the pointer to the BSS into r30. Fantasm can rename registers to anything you like (with the "reg" directive), so if we call register 30 "bss", then we can access global variables with statements such as my_variable(`bss). Note the use of the ` character to identify the name as a register name.
Local variables are stored on the stack. The stack pointer
(r1, although Fantasm also knows it as "sp" as well)
points to a free area of memory and is generally used to store
temporary data such as local variables, return addresses (so a
subroutine can return to its caller) and sometimes parameters
(data that subroutines work on; passed to them from the caller;
in PowerPC parameters are nearly always passed in registers because
there are a lot of registers, and accessing registers is far faster
than accessing memory).
Local variables, as previously noted, are accessible only to the current subroutine. In Fantasm 5 we can define local variables with the "local" macro and then access them via the stack pointer - abbreviated to "sp". The code will look something like this:
For this to work correctly, at the start of the routine we need a list of variable definitions along with a macro that sets up the stack correctly, and at the end of the routine we need a macro that will clean up the stack before we exit the subroutine. These macros are called:
reset_locals - does as it says, resets the local variables
macros and need to be used before you use the local macro.
local - define a local variable. This one has a size character tagged onto it, such as local.w - defines a local variable of size word. It is used as:
sub_entry - the macro that performs the stack set up at the start of a routine.
sub_exit - the macro that performs the clean up operation at the end of a subroutine.
Thus, a typical subroutine that needs local variables (normally one uses registers because there are lots of them) would look like this:
reset_locals local.w my_var,1 local.b my_string,256 local.h my_2d_array,10*10 my_subroutine: sub_entry some code sub_exit
In the above subroutine,
my_var defines space
for a 32 bit variable.
my_string defines space for
a 256 character string and
my_2d_array defines space
for a 10 by 10 two dimensional array of halfs, or 16 bit values.
Heavy going? Don't worry about it if you find it so, it's always easier to learn by example which is where we're heading. All I'm trying to do is give you a basic grounding from which we can build.
Variables are very useful, but sometimes we need pre-defined data, or initialized data as it's called. For example, we may need to define some constants for our program, and what about strings - for example "Fluffy loves socks". How to we get that into memory so we can print it for example?
This is where the data section comes into play. Unlike the BSS which is set up at run time, the data section is stored along with your program on disk - it takes space, but is neccessary. The data section is set up by Fantasm (more specifically the linker) and is comprised of all the data you have defined in your source files. At the lowest level, data is defined with the "dc" directives. "dc" is an abbreviation for "define constant". The dc directive allows you to define data that will be available to your program at run time. Example:
fluffy_string: dc.b "Fluffy loves socks" align
That line defines the string "Fluffy loves socks" and calls it fluffy_string. Just for now, note the "align" directive after the definition - this makes sure the next definition is aligned correctly in memory.
When we need to access this string we can use the label "fluffy_string". Another word for "label" when referring to data and variables is "identifier" which I'll use from now on.
Fantasm takes all your data identifiers and data from all your source files and passes them to the linker which "munges" them all into the data section. A pointer to each item of data is stored in the Table Of Contents (TOC) and the data identifier for each item is related to the slot in the TOC that points to the correct data. This is neccessary because when your program is launched, the data section can go at any address in RAM - it's not always the same address, so the Mac has to "relocate" all the pointers to data. These are the pointers that are stored in the TOC. Sounds complicated right? The only practical offshoot of it is that when you access an identifier for initialized data, just remember you are getting a pointer to the data.
Another way of looking at it without the technicalities is:
The TOC is pointed to by a register called rtoc, which is really just r2. This register points to a program's Table of Contents. This is simply a table that points to all initialized data (in the data section) that we gave identifiers to. Again, please forgive me but I am oversimplifying things a little, we can have code pointers and pointers to function descriptors in the toc as well, but we need things as clear as possible at this stage.
Thus a fairly typical load may look like this:
This will load the address of fluffy_string into r3, because the rtoc contains pointers to the data - so we're loading the pointer to the data into r3.
We could then load the first character of the data as follows:
"lbz" means Load Byte and Zero. (r3) means the contents
of the address pointed to by r3, and not the contents of r3 itself.
In this case, the lower byte of r3 would now contain the character "F".
So, lets summarize what we've covered so far:
1. lwz is an instruction that Loads a word into a register.
2. stw is an instruction that Stores a word into memory.
3. li is an instruction that loads a 16 bit value into a register and sign extends it.
4. movei is a macro that loads a 32 bit value into a register.
5. Variables come in two forms; local and global.
6. Global variables are accessible to any part of the program and are stored in the BSS.
7. Local variables are only accessible to the subroutine they are defined in (not strictly true in assembly language by the way - you can do anything you like, but this is the rules for beginners).
8. Local variables are set up with some macros called reset_locals, local, sub_entry and sub_exit.
9. Initialized data is stored in the data section and accessed through the TOC via register rtoc.
10. The TOC contains pointers to data, not the actual data itself.
11. Identifier is another word for "label" when used with respect to data. The word "label" is used for code, "identifier" for data.
Phew, lets give it a go hey?
OK, first make sure Macsbug is installed on your machine. To do this, press the Apple and Power On keys on your keyboard. If Macsbug pops up, lovely jubbly. Type G <return> (<return> means hit the return key on your keyboard). "G" means carry on running, or Go.
If not, if you get the little dialog box with a prompt that looks like this:
then you do not have Macsbug installed. Again type G <return> to get out of the box.
To install Macsbug, drag it over your System folder and restart your Mac. You can get Macsbug from Apple for free.
Ok, so Macsbug is installed - this will allow us to explore our programs as they are running. Now we need to write that little program above and step through it to ensure r3 get's loaded with the pointer to fluffy_string.
I've uploaded this simple project to the server - download it by clicking here. It's a Stuffit archive, so if your browser doesn't do it automatically, you may need to unstuff it using Stuffit Expander (tm).
example1: entry stdx r1,r2,r3 lwz r3,fluffy_string(rtoc) *r3 points to fluffy_string blr *simple end of our program - branch to link register. **************Data*************** fluffy_string: dc.b "Fluffy loves socks" align ***********End of program********
Lets just run through it. "example1:" is the label, or name of this program. "entry" tells Fantasm where the program starts. "stdx r1,r2,r3" causes the processor to stop in Macsbug (because it's a 64 bit instruction and a 32 bit processor doesn't understand it) so we can see our program. There is a macro that does this, called "Debug" but we won't use that yet. "lwz r3,fluffy_string(rtoc)" we've already covered. "blr" is a PowerPC instruction that means Branch to the address contained in the Link Register. This instruction was covered briefly in Chapter 1. In this case it ends our program.
After you've downloaded and expanded the project, open it from Anvil (it's called "example1_prj"), then build it (APPLE B) and then run it (APPLE R). Hopefully you should now be in Macsbug. Down the bottom of the screen you should see something like this:
020CA238 *dc.l 0x7C22192A | 7C22192A 020CA23C lwz r3,0x0004(RTOC) | 80620004 020CA240 blr | 4E800020
The line that reads
*dc.l 0x7c22192a is our "stdx
r1,r2,r3" and is how we broke into Macsbug (with an illegal
instruction). Below that we can see our assembled two lines of
program, the lwz and the blr.
If you enter the following command into Macsbug (where <CR> means press the return key):
we will skip over the illegal instruction to point to our load instruction, lwz. We can run this instruction by holding down the APPLE key and pressing the "S" key. r3 will now contain the address of fluffy_string. We can check by displaying the memory that r3 points to with the following Macsbug command:
We should hopefully see that r3 does indeed point to the string "Fluffy loves socks".
Note: You will see the string "Fluffy loves soc". This is because the dm r3<CR> command only shows the first 16 characters. To see all the string press the <CR> key a couple of times.
and your Mac should run as normal.
Just a quick Macsbug note here. If you have more than one monitor, it may be worthwhile making your start up screen anything other than your main screen. Macsbug always works on the startup screen, so this way you can see Macsbug and whatever your program is doing at the same time.
And that is a lot to take in in one chapter. In the next we'll look at logical operations (sorry, but it has to be done) and a more complex program that utilizes all we've covered here - it may even do something! In the meantime, you may like to have a look at the "PPC_graphics_demo" example that can be found in the examples folder next to wherever you installed Fantasm 5. See if you can figure out roughly what's going on, but don't worry if it just looks scary.