(To be viewed in Helvetica Size 12 on US Letter paper) ################################################################ ################# ******************************************* ################# ## ## ## ---=== Assembly for Beginners v2.0 ===--- ## ## ---=== CrackIt 1.0 ===--- ## ## ## ## by: ProZaq ## ## ## ################# ******************************************* ################# ################################################################ --==< Preface >==-- If you have ever been to my homepage or checked out the file ÒCrack-It-UpÓ you might have noticed a file called Assembly for Beginners. Well, this is based on the same file. The big difference is that almost three years has past since I wrote the first version of Assembly for Beginners. Back then I was a novice to both assembly and writing programs for the Mac. I am still far away from being good at writing programs using the Mac ToolBox, but at least now I have a helluva lot more experience and a lot more knowledge on both topics. I have basically kept the structure of the old file but rewritten most of the explanations. As always I tried to remain as clear as possible in my explanations but some of the topics are rather hard to explain so if you see any room for improvement let me know! You can find my e-mail address at the end of this file. NOTE: Some expressions described in this file may not be the same for all compilers. These examples were created to be used with Power Fantasm. To be specific, different compilers might use different ways of to signal the start of subroutines. --==< Intro >==-- Have you ever had a look at C, C++, or Pascal? Did you feel dizzy after reading the first 10 commands? Well, ÒYou ainÕt seen nothing yetÓ! Assembly, being only one level away from the user friendliness of programing in binary, is in some respect the most difficult way of writing programs for your Mac. It is, however, the most efficient way of programing. Whatever is written in assembly is for sure faster than anything written in C, or Pascal or, God forbid, Applescript! Another benefit of assembly, in my point of view, is that you only need to learn a set of about 60 (if youÕre using a 68k processor you need to know 56 basic instructions) commands (out of which you use maybe 30) instead of the bunch of commands used in the other programing languages. Since anything you write in assembly language is directly involved with the RAM, the BusÕ, and other hardware components of your Mac, you will first need to have an understanding of the different components of your computer. This file will deal ONLY with the 68k chip, and how to program a computer that uses chip. However, since the PPCÕs can simulate the 68k processors there should be no problem with writing programs in 68k for the PPC platforms. Also, writing programs in PPC is a bit more involved, if you are a beginner then itÕs a good idea to understand the basics of programing a 68k Mac before attacking a PPC platform. So what exactly IS assembly? Every kind of processor has a set of Òbuilt inÓ commands; a string of ones and zeros that are sent to the processor which in turn executes the command. To make things a bit simpler each of these commands has an equivalent represented by a three to four letter long abbreviation. Assembly commands refer to this set of commands. Therefore, when you program in assembly you use commands that can directly be translated into a string of ones and zeros (binary) that the processor can understand. If you use a high level language such as C or Pascal, then in essence you let the compiler convert your code into assembly commands. Thus if you have a ÒbadÓ compiler it might use a complicated set of routines to achieve something very simple. So when you write in assembly the speed of your program depends on you not on a compiler. --==< Basic Terminology >==-- - The Processor - this is where all the commands you create are sent and processed. - The Bus - where the information travels between the processor and the memory. - The Memory - place where data is stored. Think of it like a bunch of boxes with own addresses where the processor can store data. - Registers - places where information is stored. There are two types of registers for 68k processors: Data registers and Address registers. Data registers are used to hold information, such as numbers. Address registers are usually used to point to an area in the memory where a piece of data can be found. For example, if a simple arithmetic calculation is done, it would be done using the the data register and then the answer would be stored in the memory using the address registers. There are 8 data registers and 8 address registers, numbered d0-d7 and a0-a7 (a7 is reserved as the stack pointer). The purpose of registers and their uses will become a lot clearer when you actually start programing! - Stack Pointer - a register where data can be stored temporarily. As a matter of fact this is address register 7 (more about this later). The way it works is that you take data and push it on the stack. Then you take another piece of data and push it on top of the first one. Then at any time you can remove these pieces of data, in the proper order of course. Think about it as stacking paper sheets on-top of each other; if you put three sheets of paper on top of each other, and you want to see whatÕs on the bottom sheet then you have to remove the two on top. - Addressing - using information in the registers. - Program Counter - a special address register that keeps track of the address of the next assembly commands to be executed. - Number Systems - if you donÕt know what this is, you should think twice about reading on! IÕm sorry but I wonÕt go into what the different number systems are. Ask your math teacher! IÕll just let you know that in assembly language itÕs of extreme importance that you have a way of converting between decimal and hexadecimal numbers! - Bytes, Words, and Longs - refer to the length of a number. In assembly a byte refers to a number the length of two hex digits, a word refers to a number the length of 4 hex digits and a long is the length of 8 hex digits. For example, a byte may be AF, a word ABCD, and a long would be 12345678. In practical terms, except for a few exceptions, the only two uses for bytes words and longs is speed and number manipulation. For what is the use of moving 00000001 around when you can just move 01 around? And if you have the long 12345678 in a data register, you can move the last two digits around by just moving one byte. --==< Programing >==-- OK here we go... There should be a list of assembly commands included with this file. I will now explain what those commands do and how to use them. Well, letÕs start with maybe the most simple example, adding two numbers together. The way you would do it is by pushing the first number into one of the data registers, push the second number into a different data register and add the two data registers together. LetÕs say we were adding 1FE and 2C together, in assembly you would do it like this: move.l 1FE,d0 *moves number 1FE into data register 0 move.l 2C,d1 *moves number 2C into data register 1 add.l d1,d0 *adds the number in data register 1 to that in data register 0 This is an easy example and I donÕt think there should be any problem following it. Notice how I used longs (specified by the Ò.lÓ part after the ÒmoveÓ and ÒaddÓ commands). In reality when you would put the long 1FE onto d0, you actually push the number 000001FE, onto d0. The above procedure can be done like this as well: move.w 1FE,d0 move.b 2C,d1 add.w d1,d0 The result would be exactly the same. However, there are a few things to point out here. Notice how in the first line IÕm moving a word and how in the second line IÕm only moving a byte. Remembering that a word can hold up to 4 digits and a byte can hold only two. Now, the last line takes the word in d1 and adds it to the word in d0. Afterwards it stores the new value in d0. Now try to figure out what this does: move.w 1FE,d0 move.b 2C,d1 add.b d0,d1 Here we changed only the last line. Now, only the byte stored in d0, will be added to d1. This means that only FE will be added to 2C, because a byte can only hold two digits and therefore only the last two digits were added up. Can you spot the potentials for number manipulation? This is why it is important to keep track of which register holds what size of a number! And now for a bit more complicated one: move.l 12345678,d0 *line 1 move.l 10,d1 *line 2 loop: *line 3 move.l d0,d2 *line 4 mulu.l 10000000,d2 *line 5 divu.l 10000000,d2 *line 6 add.b d2,d3 *line 7 mulu.l 10,d3 *line 8 divu.l d1,d0 *line 9 bra loop *line 10 Believe it or not this turns the number 12345678, in d0 into 87654321. IÕll start by explaining the different instructions. In line 3 the expression Òloop:Ó is used. What this does is that it creates a subroutine named ÒloopÓ. Subroutines are what programing is all about. These are a set of instructions that are created for completing a specific task. Whenever you need that specific task done you just tell the processor to execute the commands in the subroutine and then return to where it left of. In most programs you would have a main loop, along with a bunch of subroutines, and whenever the main loop would need to do something it would go to a certain subroutine. For example, a simplified word processor, would have a main loop in which it will wait for a key to be pressed. Once that happens a subroutine would be called in which the key is printed on the screen, and after thatÕs done the processor would go back to executing the main loop. Another importance of subroutines is that you can use conditionals to branch to them. For example, when you enter a password the program might check the password and if it is correct it would Òbranch toÓ (execute) a certain subroutine. If the password is not correct it would branch to another subroutine. In the above example the subroutine is called ÒloopÓ, and by putting the colon behind it, the compiler knows that itÕs a subroutine. The last command, means branch always to the subroutine ÒloopÓ. This is the equivalent to the ÒgotoÓ command in BASIC and C. Once the processor reaches this command it will find the subroutine and execute whatever is in it. The instructions ÒdivuÓ and ÒmuluÓ mean divide and multiply using unsigned arithmetic. IÕm not going to get into what signed and unsigned bits are because that is not for beginners ; ) Let us now follow the code through: First two lines should be pretty clear. Line three tells the processor that a subroutine called ÒloopÓ will begin. Line 4 moves the hex number 12345678, into data register 2. It is important that you realize that whatever is in a data register will be regarded by the processor as a hexadecimal number (thus hex 12345678 = dec 305419896)! Line 5 multiplies 12345678, by 10000000. What does this do? Get out your scientific calculator, or drop into MacsBug, and try it out. It should give you the number 123456780000000. Remember that a data register may only hold a long, meaning a number thatÕs 8 digits long. Therefore after line 5 is executed d2 will contain the value 80000000. And after dividing this by 10000000, d2 will contain the number 00000008. Cool huh? We had to do all these things just to take the last digit of d0. All the other lines should be quite understandable for you, we add 8 to d3, multiply it by 10 (giving us 00000080). Then d0 gets divided by d1. Why? You will see in the next loop! After all this is done, the processor reaches the BRA command and branches back to ÒloopÓ. What happens now is as follows: since d0 is now 1234567, by the time the processor reaches line 7 d2 will contain the number 7, then that will be added to d3, so that d3 will contain the number 87, then it gets multiplied by 10 giving us 870, and the same thing happens again. And after a couple of loops d3 will contain the number 87654321. Ta - Tam! The only problem is that after the 8th loop, there will be a Òdivided by zeroÓ error, since d0 (containing 0 ) will be divided. So how do we make the subroutine repeat itself 8 times only? Like this: move.l 12345678,d0 move.l 10,d1 clr.l d5 loop: move.l d0,d2 mulu.l 10000000,d2 divu.l 10000000,d2 add.b d2,d3 mulu.l 10,d3 divu.l d1,d0 add.b 1,d5 cmpi.b 7,d5 bne loop rts Here I added 3 more lines. In line three data register 5 is cleared (set to zero). Then in line 11, d5 is increased by one. In the next line the compare command is used, and the number in d5, representing how many times the loop has been executed, is compared to 7. Why 7? Because at the first loop d5 had the value of 0 thus being the first in the eight loops. The last line means return from subroutine, and here it means the end of our program. If this code, however, would only have been a subroutine in a big program then the program would first branch and execute the subroutine and once the rts command is reached it would continue from where it left off (after the execute subroutine command). IÕd like to point out that this is only one way of solving the above problem. There are several other ways (maybe even easier) to achieve the same results. For example using the ÒdbneÓ command. --==< Pointers >==-- I read a book on C once and the author was putting a great emphasis on how much better C was than Pascal since it allowed the programmer to use pointers. Well, I can promise you this, once you get the hang of pointers in assembly youÕll curse every time you have to use them in C! I know... I did... Pointers are used through address registers. And the idea behind it all is that an address register points to a part of the memory with a special address, and at that address you can store data. LetÕs say that a0 points to the part of the memory with the address 100. And lets say that at the memory location 100 the number 12 is stored. At the memory location 101 the number 34 is stored, at 102 the number 56 is stored and 103 contains 78. I hope that you noticed that all these numbers were bytes, numbers that are two digits long. So you could store the above numbers at the appropriate places in the memory using the below script: movea.l 100,a0 *line 1 move.b 12,(a0) *line 2 movea.l 101,a0 *line 3 move.b 34,(a0) *line 4 movea.l 102,a0 *line 5 move.b 56,(a0) *line 6 movea.l 103,a0 *line 7 move.b 78,(a0) *line 8 First let me explain the difference between Òa0Ó and Ò(a0)Ó. When the expression Òa0Ó is used without the parenthesis that means that the command is referring to the address to which a0 is pointing to; in this case 100. Ò(a0)Ó refers to the data held in the value of a0; in the above case Ò12Ó. So, at line one, with the use of the move address command, we make address register zero ÒpointÓ to the part of the memory which has the address 100. Line two, in turn, moves the byte Ò12Ó into the memory space which has the address 100. Think of the parts of the memory like mailboxes where each mailbox has a special address (in our case 100-103) and can store numbers one bytes long. It is important that you realize that one block of memory can only store a number the length of a byte. If we donÕt put parenthesis around the address register we tell the processor which mailbox we want to mess around with. If we do put parenthesis around the address register we tell the processor that we want to put something INSIDE the mailbox to which the address register is pointing. DonÕt worry, with a bit of practice you get used to the whole concept of pointers! ThereÕs a faster way to do the above example: movea.l 100,a0 move.b 12,(a0)+ move.b 34,(a0)+ move.b 56,(a0)+ move.b 78,(a0)+ The first line is the same, it points the address register a0 to the part of the memory with the address 100. By adding a plus sign at the end of the second line we do what is called incrementing. Basically we increased the address to which a0 points to by the one (the length of a byte). So after the second line a0 will be pointing to 101. But before that happens Ò12Ó is moved into the memory at address 100. And so forth. ThereÕs an even faster way of achieving the same result: movea.l 100,a0 move.l 12345678,(a0) Notice how until now I was moving bytes into different parts of the memory. But now, I moved a long, meaning an eight digit long number. And since one block of memory can only hold a number the length of a byte, when I moved the long Ò12345678Ó to address number 100, Ò12Ó ended up in the memory at 100, Ò34Ó ended up in 101, Ò56Ó ended up in 102 and Ò78Ó ended up in 103. Let me try to illustrate how the memory blocks at addresses 100-103 are filled up when different numbers with different length are stored at those addresses: | 100 | 101 | 102 | 103 | --- address of memory | 12 | 34 | 56 | 67 | --- data stored in the memory at the address --byte-- --byte-- --byte-- --byte-- --- when storing four bytes at 100,101,102,103 ------word------- --------word------ --- when storing two words at 100, 102 -------------------long---------------- --- when storing one long at 100 I hope that you now have started to understand the concept of pointers and that you now have a good feel for the potentials of number manipulations in assembly. --- The Mac OS --- Well, now you know how assembly works. But if you donÕt know how the Mac OS works it is sort of difficult writing a program for it (unless you want to rewrite several parts of the OS). In reality when you write something for any WIMP (Windows, Icons, Menus, Popup menus) operating system you let the Operating System (in our case the MacOS) do most of the work for you. For example, when you want a window to appear, you would tell the OS a couple of things, like size and the title of the window, and then you let the OS do the rest for you. You would use so called A-traps. These are subroutines created by the OS developers that can be called upon by your application whenever they are needed. For example, to display an alert box (that is saved in the programÕs resource fork, and assuming that d0 has the value of the resource number of the alert) you could use the following code: clr.w -(sp) move.w d0,-(sp) clr.l -(sp) dc.w alert move.w (sp)+,d0 Now that doesnÕt look complicated, only you knew what the hell it meant! First of all sp refers to the stack pointer (address register 7, remember?). And in some ways the sp works the opposite to other standard pointers. The negative and positive signs before and after sp, refer to whether the sp is incremented or decremented. Wow, thereÕs two expressions for you! All right, remember how an address register points to an address in a memory? LetÕs say that sp is pointing to the location 100 in the memory. Next the word ÒabcdÓ is put into the memory at location 100. Now, remember how the stack pointer works on a principle of pushing things on and off it? So if after ÒabcdÓ was pushed on it, and we wanted to put something else on it as well, weÕll have to decrement the sp; meaning decrease 100 by 2 (since weÕre moving a word onto the stack). After the decrement the sp should point to 98. And once ÒabcdÓ is taken off the stack and it is incremented it should point to 100 again. Well thatÕs the idea behind it... This is a bit complicated and itÕs not necessary that you really understand why you have to subtract from the address when you in fact are adding things onto the stack. The only thing you have to remember is that you use -(sp) when pushing something onto the stack and (sp)+ when youÕre taking something off the stack. The rest you donÕt have to worry about! In the above example the first line clears a space for a word on the stack pointer. A lot of A-traps require you to pass an empty string because they might return a value, such as an error value if something went wrong. The next line pushes the id of the alert you want to use onto the stack pointer. Then another clearing, followed by the dc.w (define constant word) command. In this case itÕs the dc.w command that actually calls the A-trap Ò_AlertÓ. However for the above to work the variable ÒalertÓ has to be set equal to the OS number of the Ò_AlertÓ A-trap namely A985. Unfortunately only a very limited amount on information is available on how to program the MacOS using assembly language. As a matter of fact it is difficult to find up to date information on the ever changing updates on the MacOS over all. And trying to figure out how an A-trap is supposed to work using the Òtrial and errorÓ way is not the best way of programing. Especially not if youÕre writing assembly code for the PPC. IÕve never had worse freezes then when I was trying to write PPC code in assembly. Once my laptop went more or less dead; I couldnÕt break into MacsBug, I couldnÕt restart using the apple-ctrl-reset key combination, and on top of all the sleep-indicator light lit up. Anyone who has a laptop knows that, that thing is supposed to blink when the computerÕs asleep, but to light up? Never! Anyway, what I was planing to say was that assembly is the most efficient way of writing programs for the computer, but in some respects its also the most difficult way of doing it because you donÕt have a compiler telling you how to pass information to the OS. And that is a very big disadvantage! So the solution lies in the golden middle path. Whenever you have to write fast code write it in assembly, but if you have to face a lot of OS calls, I recommend using another language. As further reading I strongly recommend ÒA beginnerÕs guide to Mac assembly programingÓ by Lightsoft, the developers of PowerFantasm. It is a very good file and contains information on Power PC assembly as well. --==< The End >==-- ProZaq **************************************************** ÒSo the people turned to their God and asked: -Oh Lord, will you take away pain and misery? And he replied: -No, but IÕll give you ProZaqÓ **************************************************** Do me a favor and if you wanna spread this file around, donÕt change anything in it! If you have any suggestions or just wanna get in touch with me you always can reach me at prozaq@usa.net.