The main difference is that 32 bit assembler is not cursed with the problems and restrictions of segment arithmetic and is both much clearer and far more powerful.
"08 May 98 | SLH | ~ | hutch1.htm | The supression and resurrection of assembler programming. | papers | ~ | fra_0116 |
16 May 98 | SLH | ~ | hutch_61.htm | The Bridge: In Pursuit Of Lost Knowledge | papers | ~ | fra_011C |
21 May 98 | SLH | ~ | hutquest.htm | THE QUEST: Building the launch pad | papers | ~ | fra_011F |
29 May 98 | SLH | ~ | hutch28.htm | Software warriors through the warp | papers | ~ | fra_0123 |
05 June 98 | SLH | ~ | hutch_65.htm | The Eye Of The Warrior (a "graphical" windows' paper) | papers | ~ | fra_0128 |
10 June 98 | SLH | ~ | hutchif1.htm | The iron fist (Keeping The Crackers Amused) | papers | ~ | fra_0129 |
15 June 98 | SLH | ~ | hutsting.htm | Applying the sting | papers | ~ | fra_012C |
SLH > END GAME > > Applying The Sting > > For the modern software warrior who has done their penance and paid homage > to the great god of visual garbage, its time to put aside the trivia of > endless interface graphics and wield the weapon of war in the way it was > intended. A minimum size, fast and efficient template, the mastery of > resources and controls and the easy manipulation of the interface in terms > of its appearance is a formula that is without peer in the world of software > design. > > With some variation between compilers, this package should compile at around > 20k depending on how much space is taken up in the resources which is one > hell of a lot better than 200k or worse that you get from the RAD vendors. > > How you proceed with it depends on what type of application you have in > mind. If you are writing small specialised applications, then you will > probably add different modules which group related functions together so > that the project is well laid out and easy enough to extend and maintain. > > If however, the application you have in mind is going to be a multi-megabyte > monster, then you have to think about what is the most efficient way to > write it. You could in fact write it the same way as a small specialised > application but even if it is well written, fast and compact code, the load > time and the memory demand will tend to make it sluggish, particularly if > the machine it is run on does not have large resources available. > > One of the few good things that came out of the early versions of windows > was dynamic link libraries. When you consider that the early versions of > windows would run on a 286 with one meg of ram, albeit, only just, you > understand where the need came from to be efficient in the use of system > resource. > > Dynamic link libraries solved one of the problems that was left over from > DOS application programming, how to get capacity in excess of the available > memory limitations. The idea of having a separate file that carried a > collection of useful functions that could be called on demand meant that > you could have a relatively small executable file that called what it needed > and unloaded it when it was finished. > > In a modern 32 bit application, this equation is still in the leading edge > of design in that whatever the viable size may be for either the operating > system or the specific hardware that it is being run under, you can deliver > more capacity than the immediate size will allow by writing the core of > your application as a fast minimum size executable and farming out the > occasional functions or objects to a DLL which is loaded on demand and > unloaded when finished. > > This extends your application's size and capacity to nearly the size of > available disk space if that is what you need to write. It also allows you > to write miniature rockets that do the same thing so that they minimise > resource demand while delivering extended capacity. > > What follows is a bare skeleton of the DLLMain function in C. There is a > reasonably broad range of C compilers that will do this job well but you > will need to chase up the specifics of each compiler to ensure that you get > it right. The nod is that not only are the M$ and Borland C compilers up to > scratch to do the job but the Zortech and Symantec C compilers are also very > grunty in the area of EXE and DLL creation. > > For the warrior who likes their code written with the flavour of bubble gum, > the PowerBasic DLL compiler is an industry standard specialist DLL compiler > that gives nothing away in terms of performance so you are not at any > disadvantage at all to warriors of other languages. > --------------------------------------------------------------- > BOOL WINAPI DllMain(HINSTANCE hDLLInst, > DWORD fdwReason, > LPVOID lpvReserved) > { > switch (fdwReason) > { > case DLL_PROCESS_ATTACH: > break; > > case DLL_PROCESS_DETACH: > break; > > case DLL_THREAD_ATTACH: > break; > > case DLL_THREAD_DETACH: > break; > } > return TRUE; > } > --------------------------------------------------------------- > The DllMain function is like the WinMain function in that you don't call it, > windows does when you call the DLL. The second parameter [ fwdReason ] is > the one that you will process when you are starting or terminating processes > within your DLL. > > It is the processing of the DLL_PROCESS_ATTACH constant that determines > whether the DLL loads or not. If your nominated processing returns "TRUE", > then the DLL loads, if it returns "FALSE" the DLL unloads. The other three > return values are ignored. > > The DLL_PROCESS_DETACH constant is where you perform any actions that you > want to happen as the DLL unloads. This can be deleting resources, closing > windows or any other process you like. > > One of the tricks when designing a DLL is to put an ordinary system dialog > box in each one of the four constants that can be processed as it gives you > an intuitive feel for what is happening when you either load or unload it > from your application. > > When you write your DLL functions in C, you need do no more than "export" > it so that it is available to a calling application as C is case sensitive. > A C DLL function skeleton looks like the following, > > DLLEXPORT BOOL WINAPI MyDLLFunc(HWND hWnd, LPSTR lpszText) > { > Write your function code here > } > > With basic, you must do a little more work to ensure that the function name > that occurs in the export list of the compiled DLL is in the form that you > want. You use ALIAS to do this. > > FUNCTION MyDLLFunc ALIAS "MyDLLFunc"(ByVal hWnd as LONG,_ > lpszText as ASCIIZ) EXPORT as LONG > Write your function code here > ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ > END FUNCTION > > In the application, if you want the DLL at startup, you declare it in your > header file as follows, > > DECLARE FUNCTION MyDLLFunc LIB "MYDLL.DLL" ALIAS "MyDLLFunc"_ > (ByVal hWnd as LONG,lpszText as ASCIIZ) as LONG > > In C, you put the following into your header file ensuring that the data > types match your function call. > > DLLIMPORT BOOL WINAPI MyDLLFunc (HWND, LPSTR); > > To check the exports in your compiled DLL, you use dumppe.exe to make sure > that your function name has compiled the way you want it because if you get > the case wrong, your app will not be able to find it when it is called. > > There are basically two way that you load a DLL, either at startup as is > done in Visual Basic or by using the LoadLibrary function. In terms of > performance, there is little advantage in loading a DLL at startup as it > increases the load time of the calling app to that of simply writing the > code in the DLL into the app directly. > > The only reason why you bother if you have the option to do both is to use > a common block of code between a number of different applications that you > may wish to run at the same time. > > Using the LoadLibrary() function and the corresponding FreeLibrary() > function gives you the capacity to only use the resources occupied by the > DLL when you need its functions in the calling application. > > Encapsulated Applets > ~~~~~~~~~~~~~~~~~~~~ > One of the smart things you can do with dynamic link libraries is write > complete working applets within them so that you can call a complete > "object" from you application. If for example, you write accountancy > software, you may want a specialised calculator that not only does the > normal number crunching but other profession specific functions. > > One of the trick ways of writing a DLL of this type is to write it as an > EXE first because they are far easier to debug in that form. There are a > couple of tricks involved in doing this as you need to be able to pass the > applications "hInstance" and "hWnd" to it so you write the WinMain function > with an eye to modifying it later. > > When you have the applet up and running as an executable file, you convert > it to a DLL by first writing a DllMain or LibMain function as we have seen > above, Then you modify the WinMain function in it so that it is a function > call that is exported. > > FUNCTION SmartCalc ALIAS "SmartCalc"(ByVal Instance as LONG, _ > ByVal hParent as LONG,) _ > EXPORT as LONG > > Both Instance and hParent must be declared in the header file for the DLL > so that they can be "seen" all over the DLL. You "assign" the passed > Instance parameter to hInstance, > > hInstance = Instance > > and your applet then uses the instance handle of the calling application. > > If the applet has a simple interface that does not call any dialog boxes, > you may not need to use the hParent parameter but if you call any system > dialog boxes you need to use the hParent rather than the applet's "hWnd" > to avoid an unusual problem. > > The dialog boxes will run OK from the applet's hWnd but if you close the > application without closing the applet first, it will GP fault on exit > because the DLL applet no longer has a valid instance handle. You solve > the trash on exit by closing down the applet with a, > > SendMessage hWnd, WM_SYSCOMMAND, SC_CLOSE, 0 > > called from the DLL_PROCESS_DETACH part of the DllMain or LibMain function > but it will still GP fault on exit if you have a system dialog box displayed > when the calling application is closed down. You solve this second problem > by using the passed hParent for the dialog boxes instead of the applet's > "hWnd" so that on closing the main application, any of its child windows > are closed automatically. > > The win from re-writing the executable as an applet in a DLL is that you > can pass far more complex data in a function call than you can pass on a > command line to an executable and you can also get a return value from it. > > With some imagination, there are many things that you can do with dynamic > link libraries, applets, your own custom controls and of course, functions > packed in convenient ways for use by your aplications. > > When you think of processors running at hundreds of megabytes per second, > you would think that things should start to happen fast yet so much of what > you see is not even as fast as it was in DOS or 16 bit windows. Trapped in > the endless manipulation of interface components, a linear 32 bit processor > is not given the opportunity to really perform but below the glitzy surface > is enormous power waiting to be tapped. > > The Rocket Trip > ~~~~~~~~~~~~~~~ > This is where the real fun stuff starts, no more "Mister Nice Guy", no > compiler writer holding you hot little hand, just no holds barred use > of the processors native instructions at speeds that will leave the > uninitiated breathless. This has the feel of getting out of your Sopwith > Camel biplane and into the starship Enterprise, winding it up on impulse > power and then flicking the switch for warp drive. > > There is an option available to warriors who are writing in C to write the > following examples directly in an assembler environment if they have one > available. You will need to check the literature of either TASM or MASM to > get the details of how to set up an external module in terms of parameter > passing and calling conventions but it is an attractive option in that you > can use the macro capacity of a full assembler for later development of > more complex code structures. > > You don't pay any penalty by writing modules in inline assembler except that > the macro capacity is not available and this is how the following code is > written. > > Data Types > ~~~~~~~~~~ > If there is one thing that needs to be driven in with a large hammer, it is > the need to understand the size of your data types. In 32 bit assembler you > have three native data types, > > Byte DB = 00 ------------ hex > Word DW = 00 00 --------- hex > Dword DD = 00 00 00 00 --- hex > > You can think of each one as an appropriate size "can" for the right size > piece of data. Higher level languages construct their data types out of > these fundamental building blocks. C had at last count 59 data types but > when you look in the header file, they reduce down to these three sizes. > > The Data Manipulation Instructions > ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ > We start exploring the built in capacity in the processor to move data > around at high speed with a deviation of the [ movs ] instruction, "movsb", > move string in byte size chunks. > > This instruction has to be set up and used in conjunction with other > instructions to perform its function of copying bytes from one location in > memory to another. It uses the architecture of placing a number of the > correct data size in the ecx register, it then loads a pointer to the Source > block of memory into the source index [ esi ], loads a pointer to the > destination block of memory into the destination index [ edi ] and finally > it requires the use of a conditional repeat instruction [ repnz ]. > > mov ecx, numBytes ; number of bytes to copy > mov esi, adrSource ; pointer to source > mov edi, adrDestination ; pointer to destination > repnz movsb ; repeat byte copy while ecx not = zero. > > This combination decrements ecx for each byte copied until it is zero where > it then exits the loop. You then have the contents of the Source copied into > the Destination. To set this up in a working function, you must allocate > two blocks of memory, one which has the source data in it and the second > which is either the same size in bytes or larger. If it is not at least the > same size, you will get a page fault from overwriting memory that your app > does not own. > > In the following function, you pass a pointer to both blocks of memory and a > long integer for the length. To re-write this function in C inline asm, you > will need to pay attention to the passing of parameters to ensure that you > are passing pointers to both source and destination. In basic, you use > either the VarPtr() function or the StrPtr() function if your data is > dynamic string. > --------------------------------------------------------------------------- > FUNCTION MemCopyB(ByVal Source as LONG, _ > ByVal Dest as LONG, _ > ByVal ln as LONG) as LONG > > ' Small mover, [ movsb ] > ' ~~~~~~~~~~~~~~~~~~~~~~ > > ! cld ; copy forward in string > ! mov ecx, ln ; copy byte count into ecx > ! mov esi, Source ; copy source pointer into esi > ! mov edi, Dest ; copy destination pointer into edi > ! repnz movsb ; repeat not zero, move string BYTE > > ! sub ln, ecx ; very limited value > > FUNCTION = ln > > END FUNCTION > --------------------------------------------------------------------------- > This function, dubbed "small mover", will clock at a data transfer rate of > about 17 Meg/Sec on a 166 meg pentium, more than fast enough for many > applications moving data in sizes from bytes to kilobytes but for moving > data in megabytes, we need to use its big brother [ movsd ] move string > DWORD. > > This is where you take the gloves off and get serious, for only a small > increase in overhead, you copy four bytes at a time instead of one. You have > to solve the problem of byte lengths that are not equally divisable by 4 and > this is done by producing a hybrid function that does the major data > transfer in 4 byte chunks and cleans up the remaining bytes in one byte > chunks. > > The following function passes the same parameters as the one above. > --------------------------------------------------------------------------- > FUNCTION MemCopyD(ByVal Source as LONG, _ > ByVal Dest as LONG, _ > ByVal ln as LONG) as LONG > > ' Big mover, [ movsd ] BURP ! > ' ~~~~~~~~~~~~~~~~~~~~~~~~~~~ > LOCAL lnth as LONG > LOCAL divd as LONG > LOCAL rmdr as LONG > > ! cmp ln, 4 ; if under 4 bytes long > ! jl tail ; jump to label tail > > ! mov eax, ln ; copy length into eax > ! push eax ; place a copy of eax on the stack > > ! shr eax, 2 ; integer divide eax by 4 > ! shl eax, 2 ; multiply eax by 4 to get dividend > > ! mov divd, eax ; copy it into variable > ! mov ecx, divd ; copy variable into ecx > ! pop eax ; retrieve length in eax off the stack > ! sub eax, ecx ; subtract dividend from length to get remainder > ! mov rmdr, eax ; copy remainder into variable > > ! cld ; copy bytes forward > ! mov ecx, ln ; put byte count in ecx > ! shr ecx, 2 ; divide by 4 for DWORD data size > > ! mov esi, Source ; copy source pointer into source index > ! mov edi, Dest ; copy dest pointer into destination index > ! repnz movsd ; repeat while not zero, move string DWORD > > ! mov ecx, rmdr ; put remainder in ecx > ! jmp over > > tail: > ! mov ecx, ln ; set counter if less than 4 bytes in length > ! mov esi, Source ; copy source pointer into source index > ! mov edi, Dest ; copy dest pointer into destination index > > over: > ! repnz movsb ; copy remaining BYTES from source to dest > > ! sub ln, ecx ; calculate return value ( little use ) > > FUNCTION = ln ' return bytes copied > > END FUNCTION > --------------------------------------------------------------------------- > Whereas "small mover" had a data transfer rate of about 17 Meg/Sec, "Big > mover" clocks at about 42 Meg/Sec on a 166 Meg Pentium, figures that are > only fantasies in high level languages. On a late model fast machine, you > will easily see over a hundred Meg/Sec. > > Reading byte data is one of the useful functions that is often performed in > computer software. We explore a variation of the [ scas ] instruction, > "scasb". > > It is another built in loop instruction that has the following logic, > > mov al, Search_Char ; copy character to search for into al > mov ecx, lenString ; load length of string to search into ecx > mov edi, lpStrng ; copy pointer to the search string into edi > repne scasb ; repeat while not equal, scan string BYTE > > This loop structure also decrements ecx until either a match is found > between the search character and the character in the string where it then > exits the loop or if no match is found it decrements to zero and exits the > loop. > > Note that the data size of the search character MUST match the [ scas ] > instruction size, "al" for scasb, "ax" for scasw and "eax" for scasd. > > The following function passes 4 parameters, a pointer to the string data, > the length of the data, the character to scan for and the starting position > in the string to start scanning from. > ------------------------------------------------------------------------ > FUNCTION ScanString(ByVal lpStrng as LONG, _ > ByVal lnStr as LONG, _ > ByVal Char as BYTE, _ > ByVal sPos as LONG) as LONG > > ! inc lnStr ; needed so last char is compared > > ! mov eax, lnStr ; copy length into eax > ! sub eax, sPos ; shorten length by start pos > ! mov lnStr, eax ; copy result into length > > ! mov eax, lpStrng ; set starting offset in string by > ! add eax, sPos ; adding the starting position to it > ! mov lpStrng, eax ; put sum back into variable > > ! cld ; scan forward in string > ! mov al, Char ; copy "search" character into al > ! mov ecx, lnStr ; set maximum count in ecx > ! mov edi, lpStrng ; copy pointer into destination index > ! repne scasb ; repeat if not equal, scan string BYTE > > ! cmp ecx, 0 ; if no matches found > ! je zero ; jump to zero > > ! sub lnStr, ecx ; subtract char pos from string len > ! mov eax, lnStr ; put value in eax > ! add eax, sPos ; add starting pos to it > ! mov lnStr, eax ; put result back into value > ! jmp TheEnd > > zero: > ! mov lnStr, 0 ; set return value to zero if no match > > TheEnd: > > FUNCTION = lnStr > > END FUNCTION > ------------------------------------------------------------------------ > This function returns a long integer which indicates where the match > character is in the string. It returns a zero if no match is found. If you > bother to clock it, this one is no slouch either. > > The next function uses two separate mnemonics to read, compare and write > data from one memory block to another. The two mnemonics are, > > 1. [ lods ] "Load String" with variations lodsb, lodsw, lodsd > 2. [ stos ] "Store String" with variations stosb, stosw, stosd > > They work in much the same way as the previous mnemonics, you use ecx as > the counter, you load pointers to data in the source index and the > destination index and you use loop instructions to decrement the counter > until it is zero where you exit the loop. > > They both use the accumulator register [ al/ah, ax, eax ] in the appropriate > data size to access the data from the source index and write it to the > destination index, this is how it is accessible for processing by the > programmer. [ lodsd ] reads a DWORD size piece of data from the source > index [ esi ] into eax and [ stosd ] to write a DWORD from eax into the > destination register [ edi ]. > > What you do with the data in the accumulator register between these two > instructions depends on what you need to do. > > The following function uses the BYTE size instructions lodsb & stosb to > read a string and convert it to either upper or lower case. > ------------------------------------------------------------------------ > FUNCTION CaseTxt(ByVal Source as LONG, _ > ByVal Dest as LONG, _ > ByVal ln as LONG, _ > ByVal UorL as LONG) as LONG > > ! cld ; read & write forward > ! mov ecx, ln ; copy length into counter > ! shl ecx, 1 ; multiply length by 2 > > ! mov esi, Source ; move Source pointer into esi > ! mov edi, Dest ; move Dest pointer into edi > > ! cmp UorL, 0 ; zero for lower case > ! jne cLoop2 ; other for upper case > > ctLoop1: > ! lodsb ; read BYTE from esi (source index) > ! cmp al, 65 ; bypass addition if below ascii 65 > ! jl loc1 > ! cmp al, 90 ; bypass addition if above ascii 90 > ! jg loc1 > ! add al, 32 ; add 32 to ascii for upper to lower > loc1: > ! stosb ; write byte to edi (destination index) > ! dec ecx ; decrement counter > ! loopnz ctLoop1 ; loop while ecx not = zero > > ! jmp outCase > > ctLoop2: > ! lodsb ; read BYTE from esi (source index) > ! cmp al, 97 ; bypass subtraction if below ascii 97 > ! jl loc2 > ! cmp al, 122 ; bypass subtraction if above ascii 122 > ! jg loc2 > ! sub al, 32 ; subtract 32 from ascii for lower to upper > loc2: > ! stosb ; write byte to edi (destination index) > ! dec ecx ; decrement counter > ! loopnz ctLoop2 ; loop while ecx not = zero > > outCase: > > FUNCTION = UorL > > END FUNCTION > ------------------------------------------------------------------------ > This function is no slouch either, it clocks at 10 Meg/Sec on a 166 Meg > Pentium. The basic framework of this function can be used for a multitude > of string replacement / stripping and similar functions as long as you work > out a way to calculate the size of the destination memory block as it must > be large enough to accept what is being written to it. > > It is worth noting that these "string" instructions are by no means limited > to the higher level language data types of string. At this level you only > have sequences of bytes and as long as you can supply a pointer to the data, > you can read and write anything in whatever data size (DB DW DD) you find > convenient. > > It is worth taking the time to write functions of the type we have addressed > in a more convenient form for the particular language that you write in. > > If you are using C and you need string type data, you would normally > allocate a zero terminated string within the function as your destination > buffer, write your source data to it and pass it as the return value. > > This is one area where the old Cadillac is showing signs of age, zero > terminated strings are leftovers from the days when you live patched a > a core dump in a crashed mainframe. The basic programmer has the option of > allocating dynamic string data on the fly and this is how it should be done > in basic with a function of the type we have addressed above. > > Pass your source string as a dynamic string, allocate the Destination > buffer with Space$(len(source$)) and then write your data from the source > index to destination index as we have done above. Then you pass it as the > return value like normal. > > Depending on how your language handles addresses of allocated memory in a > function, you may need to use the [ lea ] "load effective address" mnemonic > to place the address of the destination buffer into the destination index > [ edi ]. > > What we have explored are the 486 level instructions for manipulating data > in small, fast and convenient ways yet these instructions are the workhorses > of data movement in current processors and will remain that way for some > time to come. > > When written into you application, functions of this type will suck the > headlights out of high level languages trying to do the same things while > compiling at a far smaller size. This is where the programmer finds the holy > grail. > > For the modern software warrior who has come on this journey recovering the > knowledge that was almost lost to the current generation, this is end game > where you apply the "sting". While many suffering the RAD packages are > riding a one way ticket to oblivion, your recovery of these "ancient" arts > puts you in the driver's seat for the next generation of software. > > Intel mnemonic cracking > ~~~~~~~~~~~~~~~~~~~~~~~ > There is a technique for cracking Intel mnemonics that would not be widely > known if you didn't have at least a couple of bottles of malt handy. You > also need an object heavy enough to hold the left button of the mouse down. > An old Pentax camera does the job fine. > > Armed with this "cracking" kit, you can undertake Intel mnemonic cracking > by following the instructions, Load any one of the three Intel PDF files > into Acrobat Reader, start dragging downwards with the mouse. As it starts > to scroll downwards, sit the base of the camera on the left button of the > mouse and prop up the camera so that it does not move with the two bottles > of malt. > > Once this is going, you can sit back and watch it until it reaches the end > of the file which takes just about forever but at least you are not cramping > your hand holding the button down. When it eventually gets to the end which > seems like eternity, copy it to the clipboard and save it into a text file. > > About the only thing that can go wrong with this "cracking" technique is if > you forget while you are watching it scroll away and reach for one of the > bottles of malt. This will almost exclusively result in the camera slipping > off the mouse button and you have to start again. > > With some judicious use of the search and replace function in a good, BIG > text editor, you can get this mess into reasonably readable form without > having to use that absolutely disgusting Adobe PDF format Acrobat Reader. > > Perform this "crack" on the other two Intel files and you have a programming > manual that will not go out of date quickly. This is a point not to be lost > after having shovelled through other language manuals over time. > > Whereas most languages have changed in almost unintelligible ways over a > long period, the assembler of 15 years ago which you can still find on the > net looks very much the same as the current 32 bit assembler. In terms of > time scale, this is like digging up an ancient acheological site and finding > an inscription on the wall that you can read. > > The main difference is that 32 bit assembler is not cursed with the problems > and restrictions of segment arithmetic and is both much clearer and far more > powerful. > > Back Through The Warp > ~~~~~~~~~~~~~~~~~~~~~ > For this ancient warrior, the warp is starting to waiver and I must return > for a while to do those things that ancient warriors do, rescue the odd > damsel in distress, lay siege to a castle or two, wind up the ancient chariot > to go where many have gone before and yes, write some more code. > > In your hands is Excalibur, how you wield it will shape the world of > computing to come and in only a short time in real world terms, you will > yourselves become one of the ancient warriors. > > In recovering these near lost arts, you have re-established the connection > between the past and the future which had almost been lost in the present. > The tools of the future are built in the present from the knowledge of the > past. > > Wield your Excalibur well as the ancient warriors often roam the corridors > of the Internet in search of clever things and their eye will not miss the > work of another warrior who has maintained their professional skill and > integrity by refusing to sell out to the wide and easy path that leads to > oblivion. > > END GAME >