ML-STR.THD --- Copyright 1987 by Phil Wheeler An original compilation of Compuserve Model 100 Forum messages for use by Forum members only. One of the programming techniques pioneered here by Rick Perry in his "classic" XMODEM.100/XMODEM.312 Basic xmodem programs is the stuffing of machine language code into strings, and running that m/l code via a CALL. These messages discuss that technique. Message range: 159914 to 160344 Dates: 11/7/87 to 11/13/87 Sb: #M/L in Basic Array Fm: Stan Wong 70346,1267 To: All Help! I'm at wits end (yes this is kind of like the game of Adventure). I am trying to load machine code into a Basic array and execute it via a CALL statement. The Basic CALL is to the CD% array which is a call to 7662H (beep). Everything is loaded correctly (checked by inserting an END into the program and printing out memory contents. 10 CLEAR256 20 DIMCD%(2) 30 FORI=1TO2:READCD%(I):NEXT 40 CALLVARPTR(CD%(1)) 45 MENU 50 DATA30413,-13982 60 'CALL 7662H CD 76 62 70 'RET C9 I woould appreciate any suggestions. Fm: Tony Anderson 76703,4062 To: Stan Wong 70346,1267 There is a description of that technique in the original XMODEM files, XMODEM.HLP (DL1) and XMODEM.DOC (DL3). For example of the code, XMODEM.312. I can't explain it, but perhaps you'll see how it's done from those references. Seems I recall that the VARPTR returns where the array is located, then you call that address. Is that what you're doing? Fm: Denny Thomas 76701,40 To: Stan Wong 70346,1267 The key is to load the data statements (and array) with *exactly* what you would see in RAM. In other words, all addresses are in reverse order - 76 62 is loaded as 62 76. The two data entries should be CD 62, 76 C9 (converted to integer values, of course) That little detail drove me nuts for a couple of hours when I was writting the QHAYES programs - until I duplicated exactly what Rick Perry had done in XMODEM.312. Fm: Stan Wong 70346,1267 To: Tony Anderson 76703,4062 Tony, Thanks for the XMODEM references. I'll check them out. To answer your question, I put the m/l code in a Basic array, then do a CALL VARPTR(CD%(1)) where CD% is the code array. All addresses check out (I put an end statement just before the call and check values interactively) but the call never gets there. Such a simple idea; I must be doing something real stupid. Fm: Stan Wong 70346,1267 To: Denny Thomas 76701,40 Denny, The reverse order of the data words (low byte first) drove me nuts for a while too, but I did figure that one out already. One possibility might be the CALL VARPTR(CD%(1)) statement. I presume that the address bytes are in the correct order generated by the CALL. I'm going to check out Tony's suggestion to look at the xmodem files. Fm: Wilson Van Alst 76576,2735 To: Stan Wong 70346,1267 Denny had it right: you failed to transpose the Low/High components of the address you were calling. Your data line should read: 50 DATA 25293,-13962 Here's a short program that will take a set of DATA, read it into an array, then poke the bytes into an area protected by MAXRAM -- so that you can run a disassembler and verify that you got what you tought you were getting: 10 CLEAR256,HIMEM-50 20 DEFINTA-Z:G=50 30 FORN!=HIMEMTOHIMEM+G-1 40 POKEN!,255:NEXT 50 INPUT" No. of data entries ";D 52 PRINT" (Max. of 25)" 60 DIMM(D):FORN=0TOD-1:READM(N):NEXT 70 V=VARPTR(M(0)) 80 FORN=0TO2*D-1 82 POKEHIMEM+N,PEEK(V+N):NEXT 90 PRINT"Start disassembly at"HIMEM 999 DATA 30413,-13982: REM Your data! You can put whatever you want in line 999. The program will ask how many DATA entries there were, and tell you where to start your disassembly. After running this and conducting the disassembly, you'll have to go back to BASIC and re-set HIMEM with a CLEAR256,HIMEM+50 statement. (And if you run the program more than once without doing that, you'll keep losing 50-byte chunks of memory.) Now I've got a question for you: Why does *anyone* need an m/l array in a BASIC program to make the machine BEEP? What are you _really_ up to? Fm: Phil Wheeler 71266,125 To: Wilson Van Alst 76576,2735 NIce little utility, Van. Now all we need is one to build the data statements, given the code to be put in the string. Such programs would make a nice addition to the programming stuff in dl8. Fm: Stan Wong 70346,1267 To: Tony Anderson 76703,4062 Tony, Got things figured out. There were a number of factors working. First, my test program had an error with an address word with bytes swapped the wrong way. Second, after studying Rick Perry's XMODEM, he's got the code array starting at subscript 0 instead of 1. For example: 10 DIM CD(2) 20 FORI=0TO1:READCD(I):NEXT 30 CALL VARPTR(CD(0)) 40 MENU 50 DATA 25293,-13962 will produce a beep. The machine code is: CD 76 62 'CALL 7662H C9 'RET Turning this into integers with low byte first yields: 62 CD = 25293 C9 76 = -13962 Fm: Stan Wong 70346,1267 To: Wilson Van Alst 76576,2735 Van, Thanks for the tip. I just managed to figure out how to get it to work. I was aware of the data swapping but just made a mistake in my test program. You're right, no one would want to just produce a beep but it was only a test. What I am really doing is writing a Basic/ML program to read the option ROM socket which I have stuff with my own custom EPROM. Right now I will be using it as a disk to read programs into RAM. I've got the EPROM programmed and inserted, the programs written. The m/l in the Basic array is necessary because when I switch banks to read the option ROM I no longer have Basic around to do chores for me. I put the m/l code in a basic array because I want to avoid the HIMEM,LOMEM,ALTLCD,F-KEY conflicts that always seem to crop up with programs these days. Fm: Tony Anderson 76703,4062 To: Stan Wong 70346,1267 OK, but you should be aware that there is also the possibility that when a ML routine encounters an error, it beeps and does a RTN. I encountered that in writing the macros program, so writing a program that beeps and returns may or may not be working... if you get my drift. It may beep because it's working right, or because it isn't working. I'd try something else to see if it worked. Fm: Denny Thomas 76701,40 To: Stan Wong 70346,1267 The fact that you load the array at subscript 0 or 1 really isn't important - just as long as you call the routine at the same place you loaded it. You'll notice that ther are several entry points into the M(n) array in XMODEM.312. It's critical that you start a new routine at the beginning of an integer in the array. There is no way to call into the second half of an integer and in the M(n) array, there are NOP's placed at the end of a routine if it ends up to be an odd number of bytes. Now that you have this working, you'll find that if you want to build a routine that is a little more complex (jumps, data), you'll need relocatable code. If you figger out how Rick Perry did his jump in one of the M(n) routines, you'll have the basis for all inherently relocateable algolrithms. I have another routine that's a little more straight forward if you're interested. Fm: Wilson Van Alst 76576,2735 To: Phil Wheeler 71266,125 Funny you should mention it: I just finished writing a routine to generate those DATA strings. The technique is a little awkward, though. I write the m/l routine in ready -to-be-assembled format, then use an assembler in DL8 (the one by 72176,2507; wish I knew the author's name). It creates an OBJT.DO file intended to be poked into memory with a loader program. My routine reads the OBJT.DO file and creates a file called ARRAY.DO containing the proper DATA statement(s). The advantage to this cumbersome mehod is, good documentation -- especially valuable for an asm beginner like me. I'd be happy to put what I've got in DL8, but if you'd rather hold out for something "slicker," I won't feel insulted. Fm: Wilson Van Alst 76576,2735 To: Stan Wong 70346,1267 Glad to hear you've got things working. Re. your message to Tony about Rick Perry's pgrogram, it doesn't matter whether your m/l code starts at array variable A(0) or A(1) or A(1000), as long as you CALL it with the proper VARPTR statement. In fact you can have a single array with several m/l routines in it -- starting, for example, at A(0), A(13), and A(30). As long as each routine ends with a proper RET instruction, you can call each one independently: CALL VARPTR(A(13)):CALL VARPTR(A(0)), etc. Your EPROM project sounds fascinating. Hope you'll document how you did it and share it with the rest of us. Fm: Stan Wong 70346,1267 To: Stan Wong 70346,1267 Finally got the m/l code in the Basic array to work. This is for straightline code only. Haven't figured out jumps yet. Tried doing something like: ML(14)=VARPTR(8) where locatation 14 is the address portion of a JNZ command. Doesn't work. I'll try and figure out what Rick Perry has done and let you know. Fm: Denny Thomas 76701,40 To: Stan Wong 70346,1267 The usual technique is to find out where you want to go (the tricky part) and then push it onto the stack. You then issue a RET which uses the last address on the stack. You can use any form of the RET instruction (RZ, RC etc). Now, since your variable array ML routine is always in a different place, the only way of locating it while in the array is to either pass the address to the array with the VARPTR command OR check the 2 byte address stored above MAXRAM that tells you the address of the last CALL command (in BASIC). That address gives you the base address of your routine. Add the offset from the beginning of the routine to this address and push it onto the stack, and VOYLA - do a RET and you have just jumped to where you want to go. If you feel really adventureous, you can use that base address to store flags or data in your array/routine. I don't have my reference material right in front of me, so I can't give you specfics, but if you are interested, I can send the particulars to you. Fm: Stan Wong 70346,1267 To: Denny Thomas 76701,40 Denny, Looked at Rick Perry's XMODEM code and what he's doing is very devious and clever. Unfortunetly I can't use that technique in my program. He uses register HL and DE to cause the program to loop. I'm using all registers so I need another technique. If you think it would be of interest I can provide a write-up of the XMODEM technique (which has limited applicability). Can't understand why I can't poke in an address into my code just before the call such as: ML(14)=VARPTR(ML(8)) where ML is the m/l code array and word 14 is reserved for a jump to word 8. For instance consider the instruction JNZ : Word 13 = C2 00 Word 14 = This works out to NOP - the zero byte JNZ - the C2 byte aa1 - low byte of address aa2 - high byte of address As far as I can figure out that's what the ML(14)=VARPTR(ML(8)) is supposed to do. Thanks for the tip about RETs. This technique should work. There will be a lot of overhead since I would have to reload the stack with the loop address every iteration. Also I'm using the HL and DE registers as pointers and register as BC as a counter. Your suggestion to use location F661H(?) should work but I still think that: ML(14)=VARPTR(ML(8)) should work and is easier. Oh well, I'll try and get something working first. Fm: Denny Thomas 76701,40 To: Stan Wong 70346,1267 ML(14)=VARPTR(ML(8)) won't work because you are stuffing a decimal value into ML(14) that must be in the range of +- 32768 (thereabouts). When you try to put a value above 32768, it will truncate the result. In other words, it only works for ROM addresses. There should be no problem using all of your registers and doing the relative jump - just push your registers and then pop them prior to doing the RET or RZ. The overhead for doing the relative branch is 16 bytes. Fm: Stan Wong 70346,1267 To: Denny Thomas 76701,40 Got the jump code worked out. It's a real mess but I used your tip and pulled the address from locations F661-F662H, added a displacement and pushed it onto the stack. Instead of a JNZ I used a RNZ instruction instead. The code is real messy because I am using all the registers and have to put things onto the stack. The code fragment below illustrates the process (remember, I am using all 16-bit registers) : PUSH H PUSH D LHLD F661H ; addr of m/l base XRA A MOV D,A MVI A,16H ; loop pt. is 16 bytes from base MOV E,A DAD D ; jump addr. --> HL POP D ; recover DE XTHL ; jump addr --> stack ; recover HL RNZ ; loop (JNZ) POP D ; trash unused return addr. Hope this illustrates one approach to looping. Still don't understand why ML(14)=VARPTR(ML(8)) doesn't work. Fm: Denny Thomas 76701,40 To: Stan Wong 70346,1267 Very good, Stan! That's basically it. Unfortunately, it usually follows that if you try something tricky, it also gets more complex. As for ML(14)...., just remember that an integer can only hold a value no greater than 32767. All variables are stored in RAM at addresses much higher than that. When you try store a single precision number in an integer, you get a value that is truncated if larger than 32767. You'd have to poke each byte of ML(14) individually to store a proper address. Fm: Stan Wong 70346,1267 To: Denny Thomas 76701,40 I understand what you are trying to say relative to +- 32K for the statement ML(14)=VARPTR(ML(8)) but try the following statements in Basic: CLEAR 256: DEFINT A-Z : DIM ML(20) PRINT VARPTR(ML(0)) ML(1)=VARPTR(ML(0)) PRINT ML(1) On my machine I get negative numbers for the addresses generated by VARPTR which indicates to me that it is handling the "overflow" properly. Also try I%=VARPTR(ML(0)) PRINT I% and you get the same results. I'm still baffled. I guess I can try your suggestion of poking the address into the array but I dimly seem to remember trying that also. Perhaps I'll try again. Fm: Stan Wong 70346,1267 To: Wilson Van Alst 76576,2735 Van, I've had a chance to use that utility you wrote that allows me to use a disassembler to look at m/l loaded as data statements. It's good stuff! If it's not in the DLs, it should be. I've added on line to the program: 100 CLEAR256,HIMEM+50:END This puts himem back so that you don't have to worry about doing a CLEAR interactively (I forgot several times). No m/l programmer should be without it! Fm: Wilson Van Alst 76576,2735 To: Stan Wong 70346,1267 Thanks for your comments on the utility. I haven't put it in the DL, so far, but probably will when I can get around to writing an adequate DOC file. (I wouldn't expect a lot of demand for the thing, though, 'cause it deals with a pretty esoteric niche in the world of asm programming.) There's a potential problem with the CLEAR statement you added: since it restores HIMEM *before* you run the disassembly, there's a chance that the disassembler's program variables will clobber the very code you're trying to read. The reason for lowering HIMEM in the first place -- the only reason -- is to protect that code. I suggest going back to the "manual" method or restoring HIMEM. (Just had an idea: why not write the code to the alt. screen buffer and avoid the need to fool with HIMEM at all. Hmmm...). Fm: Stan Wong 70346,1267 To: Wilson Van Alst 76576,2735 Van, You're probably right about the CLEAR statement. I'm using DISASM.CO so it's probably got its own space allocated. I haven't had a problem (yet). I don't think that your program is for a real narrow niche but then again anything I do must be mainstream . Anyway, still think others would fin your program useful. Look forward to seeing your program in the DLs.