FB II Compiler
Understand the strucutre of an INIT
Sorry if this question seems too simple, but I've never done INIT's before with FB.
I'm doing some trap patching with the INIT shell that came with FB. Trouble is, the way they've structured it, it's very difficult to do more than one patch in the same INIT. The sample INIT is designed to read through all the code and then chug directly into the first patch that's there.
The way I got around this was by inserting my next patch using some inline assembler histrionics. It works fine, but is a little cumbersome to install and you lose access to your globals.
Does anyone know an easy way of inserting a series of patches in one INIT without having to resort to inline assembler?
If you start with the INIT example that came with FB, you'll already have everything you need. Just patch traps or react to the JGNE intercepts for any amount of things that need to be modified.
Thanks, Jamin, but DCOD's are still on my "to learn" list, along with OOP and Tolz resources. I guess I'll stick to good ol' assembler for now.
<< Just call a LOCAL FN or use ENTERPROC/EXITPROC if you want to play with your globals, FB seems to set everything up for you. >>
I think the patch has to pass through the globals section of the INIT, actually. This could be wrong. Enterprocs and Exitprocs have always confused me, since you can't see what they're doing to the stack. I like a patch to jump in midstream without doing any playing with the stack. Maybe all one needs to do to access globals from inline assembler is set a pointer somewhere (Chris?).
If you find assembler easier than DCODs, then maybe you can help me with my "Generic" trap patch (below). It seems to work when applied in an application, but not from an INIT. I have spent many hours in MacsBug trying to work out why? Any assembler help welcome!
LOCAL FN GetMyGlobal
theRealTrap& = gOldTrapPtr&
END FN = theRealTrap&
LONG IF gOldTrapPtr& <> 0
` MOVE.L (SP)+,^ReturnAddress&
` movem.l d0-d7/a0-a6,-(sp)
myReal& = FN GetMyGlobal
` movem.l (sp)+,d0-d7/a0-a6
` MOVE.L ^ReturnAddress&,-(SP)
GETTRAPADDRESS won't work with Toolbox traps, only OS traps. As far as I could see you have to port the GetToolBoxTrapAddress & SetToolBoxTrapAddress for most of the new traps, but I could be wrong.
<< Does anyone know an easy way of inserting a series of patches in one INIT without having to resort to inline assembler? >>
I resorted to the following to build an INIT/CDEV combo, as you run into similar problems:
Setup the code of the two patches as seperate projects. Compile them as DCOD resources, appended to one master resource file of a third project.
The third project will be the INIT id Zero resource that does the actual patching. It will do a GetResource on the first DCOD, detach and install it into the System heap, and then re-direct the trap. Do the same with the other DCOD resource. Cross your fingers and hope you don't have any bugs...
<< The way I got around this was by inserting my next patch using some inline assembler histrionics. It works fine, but is a little cumbersome to install and you lose access to your globals. >>
Just call a LOCAL FN or use ENTERPROC/EXITPROC if you want to play with your globals, FB seems to set everything up for you.
This is a somewhat lengthy and technical message, but I really could use some help here. I've asked this question before but now have a better understanding that will help to refine the question:
I'm writing an INIT that uses the INIT shell that came with FB II. This shell patches GetNextEvent and the patch has access to all globals and local functions. The patch also has an entry point someplace near the beginning of the whole code segment, which is determined by the value of a4 when the startup routine is running (after which all the relevant code is copied into a pointer block).
Now I've taken the shell and attempted to add to it. First step: create and install another patch. It's no problem to write the patch, but it can't have the same entry point because then I wouldn't know which trap was calling my code. So instead I stick the patch somewhere in the middle of the code segment and put in a GOTO statement to jump over it and write a few test statements in assembler. I install the patch using traditional methods.
The problem is: when my new patch is called, I have almost no access to globals. Nor can I call local fns. All of these produce address or bus errors. (Interestingly, the following will work:
but this won't:
So to avoid clunking around using low-level addressing techniques all the time, I need to find a way to access the globals.
I tried what seemed to be a rather clever technique: when the code entered my patch, I set a value (which was stored inside the code space itself) to identify which patch had been called, then jsr'd to the start of the whole code segment, then entered a second routine which was supposed to continue the patch code. But the second routine kept crashing with address or bus errors in spite of my best attempts to fix the problem.
So what I'm hoping is that there's another way to access the globals. Walking through Macsbug shows that FB uses a6 or a4 addressing to find the globals. Is there a value in a6 that I can store at startup, or a pointer value somewhere, which will give the base address for the globals? And is there a way to tell FB where the globals are so that I don't have to calculate my own offsets into the global space?
Any help will save me from a number of evenings crashing my machine and poking around by trial and error.
<< It's no problem to write the patch, but it can't have the same entry point because then I wouldn't know which trap was calling my code. So instead I stick the patch somewhere in the middle of the code segment and put in a GOTO statement to jump over it and write a few test statements in assembler. I install the patch using traditional methods.
The problem is: when my new patch is called, I have almost no access to globals. Nor can I call local fns. All of these produce address or bus errors. >>
What exactly *is* your entry point? Are you using ENTERPROC, or just jumping into a random section of code?
The official FB way to get globals is to run into an ENTERPROC. This can also accept parameters. It ends with EXITPROC, which can return a value.
If you are jumping into an ENTERPROC and you aren't getting globals, something else is wrong.
<< Walking through Macsbug shows that FB uses a6 or a4 addressing to find the globals. Is there a value in a6 that I can store at startup, or a pointer value somewhere, which will give the base address for the globals? >>
A6 is the local variable frame on the stack. It will change pretty much every time you get called.
A4 is the code resource global variable pointer. If you save this when you are in "known" code, then pull it back into A4 later, you will magically gain access to your globals. Of course, you have to be careful to save the value of A4 from when you were called so that you don't mess up another resource's globals.
If you have more questions please feel free to ask... I've done entirely too much of this kind of work...
<< The official FB way to get globals is to run into an ENTERPROC. This can also accept parameters. It ends with EXITPROC, which can return a value. >>
Okay, you talked me into it, I'll try and get ENTERPROC% to work. But already I'm confused. I used Macsbug to step through the entry to my ENTERPROC% and, contrary to the online help, did not see ENTERPROC% saving the registers--in fact, it doesn't touch the stack at all. It just seems to be setting up its own a4 addressing. Do I have to save the registers myself from within the ENTERPROC%?
Also, the FB shell uses a double patch (both a head and a tail). I'd like to do a head patch only, and not take any values off the stack, since one of the values in the routine I'm patching is a boolean, and that does not translate to FB. I was hoping to do a test patch like this:
` move.l ^gOldTrapAddr&,-(sp)
But even this doesn't work. I'm starting to miss Fantasm...
<< If you have more questions please feel free to ask... I've done entirely too much of this kind of work... >>
I really, really, really appreciate this. Thanks again.
There is a subtle but critical difference between ENTERPROC% (with the percent sign) and ENTERPROC (without). There can only (safely) be one ENTERPROC% per program. But there can be as many ENTERPROCs as you like.
ENTERPROC% is the code resource's main entry point. For an INIT, this represents the code that will be called at startup. All ENTERPROCs are alternate entry points - they can be called at any time.
<< I was hoping to do a test patch like this:
"My Patch"But even this doesn't work. I'm starting to miss Fantasm... >>
` move.l ^gOldTrapAddr&,-(sp)
I suggest putting the setup/cleanup code outside of the ENTERPROC. Whenever there are register-based traps, at least, that's the only way to do it. I did something like the following for a jGNEFilter a while back:
And that's it. I pointed the jGNEFilter to LINE "jGNEFilter". Then I poked the previous jGNEFilter value into the address LINE "returnoperation"+2. This way, when the jmp executed, its argument was the old address that the code was supposed to go to.
` move.l A1,-(sp)
` move.l D0,-(sp)
` move.l (sp)+,D0
` move.l (sp)+,A1
` jmp $00000000
Something similar should work in an ordinary stack-based trap patch.
Okay, time for another long shot from me.
I'd like to write a simple extension (INIT) that does nothing more than watch for certain applications that are running and provide a hot key switching between them. Probably something simple like using the process manager for bringing the app to the front (if that can be done from within an INIT).
No interface would be required for setting the hot key (hard coded). Sure be nice to find something to do with those Function key across the top.
Anyone done anything like this or got some bomb proof init code?
This isn't such a long shot--just use the INIT shell that came with FB. It has already patched GetNextEvent and has a case structure which includes a keydown event. All you have to do is check which key is pressed, then if it's your hot key, call whatever local fn you desire.
FB also has some excellent Process Manager code in the examples folder which will show you how to get the current process number and name. To cycle through a list of the currently running processes, look up the example code in Think Reference or Inside Macintosh: Processes (available for downloaad from the Macintosh web site).
I'm not sure offhand how to change the current process, but this information will be in Inside Macintosh (it's a short volume).
As long as you don't need to do any other patches, you'll be fine. The problems I've written about elsewhere in my own code were caused by my needing to have a number of different patches in the same INIT code. This is much more difficult to structure. Maybe if I get this new structure working properly (with Mars' expert coaching) I'll post a souped-up INIT shell on one of the websites, if people are interested.