Using Assembler Directives - Printable Version +- Mario Kart Wii Gecko Codes, Cheats, & Hacks (https://mariokartwii.com) +-- Forum: Guides/Tutorials/How-To's (https://mariokartwii.com/forumdisplay.php?fid=45) +--- Forum: PowerPC Assembly (https://mariokartwii.com/forumdisplay.php?fid=50) +--- Thread: Using Assembler Directives (/showthread.php?tid=1083) |
Using Assembler Directives - Vega - 03-03-2019 Using Assembler Directives in your Source Chapter 1: Intro Gecko Code Assemblers all have a feature called Assembler Directives. Assembler Directives are various instructions (these are NOT actual PPC Instructions!!!) that tell the Assembler about any symbols, macros, statements, etc that are in your Source and how to apply them to help assemble your Code. If you have been following my "Go From Noobs to ASM Coder" index, you will have already learned about some Assembler Directives known as "Pseudo Ops" in the "BL Trick" thread. When your ASM Codes are becoming too large/complex, it may be time to start using Directives. You may have a chunk of code that is present multiple times in your source. Why write it out multiple times when you can just write it out once and have it applied to multiple locations? Not only that, are you running into situations of having to memorize a specific numerical value for something that is commonly known by name? Directives allow you to use Names to represent any numerical value. Chapter 2: Setting Symbols You can direct specific names to any numerical value you want. This is done via the SET directive. Anything that uses the SET directive is known as a Symbol. Format~ .set name, expression name = The name ofc expression = Value/Number, but this can also be a name (more on names within names in Chapter 5) Let's say you want to set the name True for the numerical value of 1. You would write the following line anywhere in your source~ .set True, 1 You can write it out in Hexadecimal as well .set True, 0x1 With the name True set to the value of 1, you can now write True in your source wherever you need to value of 1 applied. Like this.... Code: .set True, 1 #This line only needs to present once in your source and can be located anywhere as long as the symbol is above the instruction that uses said symbol Some more Symbol examples~ .set Offset, 0x1340 .set Function_Address, 0x8045ECC4 .set Error, -1 Chapter 3: Using @h, @ha, and @l with your Symbols for Memory Addresses Having Symbols set for commonly used Memory Addresses can come in handy. Like so... .set Function_Address, 0x8045ECC4 Therefore, if you need to set Function_Address into a register (let's say r12), you can do so something like this... Code: .set Function_Address, 0x8045ECC4
Let's say we use that same address value but instead we have it called Loading_Spot, and we need to load a word value from the address. We will use r5 for the example. You would write it out like so.. Code: .set Loading_Spot, 0x8045ECC4 @ha = Use the upper 16 bits, but add 0x10000 to it. And makes next use of @l afterwards be sign-extended. @l = Use the lower 16 bits. @ha stands for High Algebraic. You need to use @ha if you are ever storing/loading to/from an Address. This is just in case the lower 16 bits of your store/load Address exceed 0x7FFF. Even if the use of @ha isn't necessary for a particular store/load, you should always be applying it as a good force of habit. Chapter 4: Using Math, Binary, and Logical Operations within Symbols Instead of writing just plane jane values for your Symbols. You can incorporate basic calculations. Like this.. .set add_values, 0x1000 + 0xB00 .set subtract_values, 100 - 50 .set divide_values, 33 / 11 .set multiply_values, 0xA0 * 0x14 You can beyond basic math and utilize basic binary logical operations .set OR_values, 0x1008 | 0x400 .set AND_values, 0x800 & 0x000 .set XOR_values, 0xF8001CFD ^ 0x01000AAB You can do non-Binary Logical Statements like this... .set logical_and_result, 1 && 2 Please note that these type of Logical Statements (non-Binary) only output a bool value (0 or 1). The above Symbol will do a logical AND of 1 and 2. If the result is non-zero, then the Symbol 'logical_and_result' will have the value of 1 tied to it. If the result is zero, then the Symbol will have the value of 0 tied to it. Other Logical Statement Examples... .set logical_or_result, 278 || 000 .set logical_not_result, !(2 && 1) #This will do a Logical AND Statement, but the Result of the AND'ing is flipped. Chapter 5: Names within Names, and Psuedo-Ops As mentioned in chapter 2, you can use names within names for Symbols. Like this... .set bowser, 0x1E00 .set auto_drift, 1 .set character_config, bowser | auto_drift Another example~ .set table_address, 0x81500078 .set index_one, 4 .set index_two, index_one*2 .set index_three, index_two*2 .set table_special_data_address, table_address + index_three As you can see, you can establish symbols that can be used directly in calculations of other symbols. You may be in a situation where you need to create a Lookup Table (via a BL Trick), and you need plain jane numerical values written in said table, but you don't wanna go thru the hassle of setting up Symbols for every numerical value. You can use what are referred to as "Pseudo-Ops". Here's a list of all of them.
There is also one more important Pseudo-Op and that is ".align X". It aligns your block of Pseudo-Op data. This is needed if your block of data (as a total) has a size that isn't divisible by 4. You can choose how to align the block of data. .align X Guide
etc etc... You SHOULD ALWAYS add in a ".align 2" after any use of any Psuedo-Op that incorporates an ASCII string. '.align 2' may also have be used if you have incorporated byte and halfword Pseudo-Ops. The great thing about .align is that if no alignment is required, your Assembler will ignore it. Thus, no extra unnecessary null bytes are added to your code. Confused? A picture is worth 1000 words. Take a look at the following picture (RAW option in PyiiASMH used so no Gecko related stuff is included).. As you can see the "Mario Kart Wii" string (with a null byte auto appended at the end) creates a block of data that isn't divisible by 4. Another term is that it's not "word-divisible". Now take a look at the next picture below... As you can see we now have a .align 2 placed below the .asciz. The block of data is now aligned and divisible by 4. A good use of Pseudo-Ops is for creating Lookup Tables for your Code. A Lookup Table is a great way to allocate a block of memory withing the code itself, and have valuable data that will be referenced/used by your Code multiple times. Example of creating a basic Lookup Table~ Code: bl lookup_table Since the length of ASCII strings can vary, it's best to place any such string(s) at the end of your lookup table and then finish it off with a '.align 2' to enforce address alignment. If you don't place all your string(s) at the end, then extra unnecessary null bytes (auto appended by .asciz) will take up space in your Lookup Table. You can also place label names (like how you would write branch destination spots) within a Lookup Table to 'point' to various items without any manual calculation needed. Example~ Code: bl lookup_table Take a good look at the addi instruction at the very end of the above source. Notice how I implemented a basic Symbol Calculation to auto-calculate the numerical value that the addi instruction needs to use, so the addition of that value with r12 will be placed in r4. Thus, r4 will contain the Memory Address that directly points to "/shared2/sys/SYSCONF". Chapter 6: Macros While symbols can be handy, they are very limited. If you want PPC instruction(s) to be tied to a particular Name, you will need to use what are called Macros. Format of a Macro~ .macro Name Optional-Arg** contents of Macro located HERE .endm **Optional-Arg is optional ofc, and will be discussed in the next Chapter. Let's say we have the following PPC instructions... Code: lis r12, 0x8000 ...And these instructions occur multiple times in your source. Well instead of 'manually' writing these batch of instructions for every single occurrence, you can write out the macro for it just once, with a name tied to it, and then literally just write out the name. Like this.. Code: .macro Load_Pointer #Write out the Macro, can be anywhere in your Source Chapter 7: Macros w/ Args Now we will discuss the Optional Arguments in Macros. With the above macro example in Chapter 6, you were 'forced' to use r12. If you wanted to use a different register and keep the original macro as well, you would have to write out a whole new 2nd macro. Well, with Argument options, you wouldn't need to resort to that. A Macro can be given arg(s) with custom names. Look at the following example.. .macro Load_Pointer register lis \register, 0x8000 lwz \register, 0x1500 (\register) lwz \register, 0 (\register) .endm As you can see, we have have the Macro equipped with 'register' as an Arg. To use the Arg within the Marco you need to prepend "\" before its name (i.e. \register). With this macro, we can now select with register to use with it. Like so... Code: .macro Load_Pointer register #Create Macro with 1 argument. Argument's name is register. The Macro Argument named as 'register' allows us to choose whatever register we want to use when referencing our Macro within the Source. Here's an another example of a Marco Arg (using custom name 'address' for the use of writing in Memory Address's) Code: .macro Load_Pointer address #Create Macro w/ 1 arg, titled 'address' Notice the comment I placed in the macro regarding @ha. Remember, that for any store/load, use @ha for establishing the upper bits of the Address! If it's not a store/load, you can simply use @h. Fyi, you are allowed to have more than just 1 Macro Arg. Like so... Code: .macro Load_Pointer register, address #NOTICE the comma separating the two Args. This is needed Chapter 8: Making Sources Region Friendly via If-Statements If-Statements are really useful for having one source be able to be quickly changed on the fly before compilation in regards to differences due to Region issues. You can have one main source with If-Statements to handle Region differences instead of having multiple slightly different Sources to accommodate every Region. MKWii has 4 regions. So writing the same source slightly different a total of 4 times would be quite annoying. Anyway, you have the following directives at your disposal.
Here is a handy template that you can use to easily handle Region-specific versions within one source. Code: .set region, '' #Fill in E, P, J, or K within the quotes for your region when Compiling! Lowercase letters can also be used. Anyway, regarding the template, it requires you fill in the Region "letter" for the source to assemble. The letter can be upper or lower case. The Assembler will assemble its contents differently depending on what Region "letter" is filled in more the ".region" Symbol. If no Region "Letter" is used or an invalid one is supplied, the Assembler will output an Error and not even attempt to compile your source. Here's an example of having region-specific Macros using the above template~ Code: .set region, '' #Fill in E, P, J, or K within the quotes for your region when Compiling! Lowercase letters can also be used. Chapter 9: Conclusion + Handy Reference Page Here's a site that covers most Assembler Directives. Keep in mind this isn't 100% accurate as the content is based on the GNU Assembler - https://sourceware.org/binutils/docs/as/Pseudo-Ops.html For an actual real world code that uses some good macros plus symbols, check out Star's Screenshot code - https://mkwii.com/showthread.php?tid=1080 |