TweetFollow Us on Twitter

Self Modifying Code
Volume Number:8
Issue Number:4
Column Tag: Article Rebuttal

Self Modifying code is a No-No!

A better way to do an event patch without self-modifying code or Assembly

By Scott T. Boyd, Apple Computer, Inc. and Mike Scanlin, MacTutor Regular Contributing Author

Note: Source code files accompanying article are located on MacTech CD-ROM or source code disks.

Mike Scanlin’s article “Rotten Apple INIT for April Fool’s” brings up a minor but essential point. The GetNextEvent patch looks like:

;1

@first
 Lea    @exitAddress, A0
 Move.L (SP)+,(A0)
 Lea    @eventRecPtr,A0
 Move.L (SP),(A0)
 Pea    @tailPatch

 DCJmpInstruction
@origTrap
 NOP
 NOP

The NOPs get replaced with the original GetNextEvent trap address by the installation code:

;2

 Lea    @origTrap,A1
 Move.L A0,(A1)

Now, consider the processor instruction cache. It’s a piece of the processor which remembers what’s at a set of memory locations. It does this so the CPU won’t have to do a memory access for recently referenced instructions. This is designed to save time. It’s a neat hardware feature.

However, the Macintosh system software doesn’t make a distinction between code and data. That’s different from OSs like Unix, which keep code and data in separate address spaces. When Mike’s code installs the original trap address with the Move.L A0,(A1), it’s putting an address into the middle of a piece of code. Unfortunately, the cache which records the new value is the data cache.

The instruction cache has no clue that instructions just changed. This is one way of doing what’s commonly called “self-modifying code”.

Self-modifying code is, in general, a bad thing to write. Apple has long discouraged, and continues to discourage self-modifying code.

This is bad in this case because the processor, if it were to execute this code right away, might believe (for some unspecified reason) that those memory locations were cached in the instruction cache. If it did, it would pull whatever had been in that location the last time code was executed from that spot, then try to execute whatever was there. The odds that the instruction cache held the value you just put into the data cache are not in your favor.

Contrast that approach with this approach:

;3

@first
 ...

 Move.L @origTrap,A0
 JMP.L  (A0)
 ...

/* branch around this, or put it somewhere else, but don’t let the PC run through 
here */

@origTrap
 NOP
 NOP

While this approach still stores the old address into a piece of code, it’s never referenced as code by the processor. It’s treated specifically as data. The instruction cache never comes into play since the original address is moved as data.

Yet another approach, which also saves a register:

;4

@first
                        ...
                        Move.L@origTrap,-(SP)
                        RTS
                        ...

TN #261: “Cache As Cache Can” discusses this topic in more detail, especially with regard to moving whole chunks of code around.

As it happens, the caches are almost certainly flushed before this particular eight bytes ever get loaded for execution, but that’s a happy coincidence, and not something you should rely on. What’s happening is that we have made several traps flush the caches (guaranteeing that there won’t be any misunderstanding about something being in the instruction cache when it’s not), but we may change our minds about which traps should flush, and when. You shouldn’t count on any given trap’s current cache-flushing behavior.

One final consideration. Putting data into code does not work if code is ever write-protected, and that may happen one day. So where can you put something when you can’t allocate any global storage (e.g., PC-relative data or low-memory globals with a fixed address)? You can use NewGestalt to register a new selector. When you call Gestalt, it can return a value which is actually a pointer (or handle) to your global data. This technique won’t work well if you can’t afford to make the trap call (like from some time-sensitive routine you’ve patched), but it works nicely if you have the time and you want to avoid putting data into your patch code.

Scott T Boyd, Apple Computer, Inc.

Mike Scanlin Says

Scott's point about stale code in the instruction cache is well taken and I deserve a thumping for having written it. I made the poor judgement call that it wouldn't matter in this case because I expected the instruction cache to be flushed between the time the patch installation code finished and the first time the patch code was executed. I hang my head in shame.

As partial retribution (and to satisfy a few requests for a non-assembly version) I have written a trap patching shell in C that doesn't use any self-modifying code (see listing below). It obeys all of the rules except for the one about storing data into a code segment (Scott's solutions have this problem, too, as he mentions). Until we have write-protected code segments, this will not be a problem.

Mike Scanlin

/*********************************************************
 * PatchGNE.c:
 * This INIT installs a patch on GetNextEvent and 
 * SystemEvent that intercepts keyDown and autoKey events. 
 * For this example, the intercepted key events are 
 * converted to lower case if both the capsLock key and the 
 * shiftKey are down (thus making the Mac keyboard behave 
 * like an IBM keyboard). However, you can use this shell to 
 * do generalized event intercepting as well as generalized 
 * trap patching (with no asm and no self-modifying code). 
 * If your patches need globals, put them in the 
 * PatchGlobals struct and initialize them in main.
 * In Think C, set the Project Type to Code Resource, the 
 * File Type to INIT, the Creator to anything, the Type to 
 * INIT, the ID to something like 55 (55 will work but it 
 * doesn't have to be 55), turn Custom Header ON and Attrs 
 * to 20 (purgeable) and Multi Segment OFF.
 *
 * Mike Scanlin. 16 May 1992.
 *********************************************************/

#include "Traps.h"

/**********************************************************
 * typedefs
 *********************************************************/
typedef pascal short (*GNEProcPtr)(short eventMask,
 EventRecord *theEvent);
typedef pascal short (*SEProcPtr)(EventRecord *theEvent);

typedef struct PatchGlobals {
 GNEProcPtr pgOldGNE;
 SEProcPtrpgOldSE;
} PatchGlobals, *PatchGlobalsPtr;

/**********************************************************
 * prototypes
 *********************************************************/
void main(void);
void StartPatchCode(void);
pascal short MyGetNextEvent(short eventMask, EventRecord
 *theEvent); 
pascal short MySystemEvent(EventRecord *theEvent);
void CheckKeyCase(EventRecord *theEvent);
void EndPatchCode(void);

/**********************************************************
 * main:
 * Gets some memory in the system heap and installs the GNE 
 * and SE patches (as well as allocating and initializing 
 * the patc 8.4  Self Modifying Codeutine that gets 
 * executed at startup time (by the INIT mechanism).
 *
 * The block of memory that main allocates will look like 
 * this when main has finished:
 *
 *                   +--------------------+
 *                   |    PatchGlobals    |
 *                   +--------------------+
 *                   |  StartPatchCode()  |
 *  GNE trap addr -> +--------------------+
 *                   |  MyGetNextEvent()  |
 *   SE trap addr -> +--------------------+
 *                   |  MySystemEvent()   |
 *                   +--------------------+
 *                   |   CheckKeyCase()   |
 *                   +--------------------+
 *                   |   EndPatchCode()   |
 *                   +--------------------+
 *
 *********************************************************/
void main()
{
    Ptr             patchPtr;
    PatchGlobalsPtr pgPtr;
    long            codeSize, offset;

    /* try and get some memory in the system heap for code
       and globals */
    codeSize = (long) EndPatchCode - (long) StartPatchCode;
    patchPtr = NewPtrSys(codeSize + sizeof(PatchGlobals));
    if (!patchPtr)
        return; /* out of memory -- abort patching */

    /* initialize the patch globals at the beginning 
       of the block */
    pgPtr = (PatchGlobalsPtr) patchPtr;
    pgPtr->pgOldGNE = (GNEProcPtr)
      GetTrapAddress(_GetNextEvent);
    pgPtr->pgOldSE = (SEProcPtr)
      GetTrapAddress(_SystemEvent);

    /* move the code into place after the globals */
    BlockMove(StartPatchCode, patchPtr +
      sizeof(PatchGlobals), codeSize);

    /* set the patches */
    patchPtr += sizeof(PatchGlobals);
    offset = (long) MyGetNextEvent - (long) StartPatchCode;
    SetTrapAddress((long) patchPtr + offset, _GetNextEvent);
    offset = (long) MySystemEvent - (long) StartPatchCode;
    SetTrapAddress((long) patchPtr + offset, _SystemEvent);
}

/**********************************************************
 * StartPatchCode:
 * Dummy proc to mark the beginning of the code for the 
 * patches.  Make sure all of your patch code is between 
 * here and EndPatchCode.
*********************************************************/
void StartPatchCode()
{
}

/*********************************************************
 * MyGetNextEvent:
 * Tail patch on GetNextEvent.
 *
 * The reason this returns a short instead of a Boolean is 
 * because we need to make sure the low byte of the top word 
 * on the stack is zero because some programs do a Tst.W 
 * (SP)+ when this returns instead of Tst.B (SP)+ like they 
 * should (which is technically their bug but, we might as 
 * well work around it since it's not hard).
 *
 * If you want to eat the event and not pass it on to the 
 * caller then set returnValue to zero.
 *********************************************************/
pascal short MyGetNextEvent(short eventMask,
  EventRecord *theEvent)
{
    PatchGlobalsPtr pgPtr;
    short           returnValue;

    /* find our globals */
    pgPtr = (PatchGlobalsPtr) ((long) StartPatchCode -
      sizeof(PatchGlobals));

    /* call original GNE first */
    returnValue = (*pgPtr->pgOldGNE)(eventMask, theEvent);

    /* do some post-processing */
    CheckKeyCase(theEvent);

    /* return to original caller */
    return (returnValue);
}

/**********************************************************
 * MySystemEvent:
 * Tail patch on SystemEvent.
 *
 * The reason this returns a short instead of a Boolean is 
 * because we need to make sure the low byte of the top word 
 * on the stack is zero because some programs do a Tst.W 
 * (SP)+ when this returns instead of Tst.B (SP)+ like they 
 * should (which is technically their bug but, we might as 
 * well work around it since it's not hard).
 * 
 * We need this patch as well as the one on GetNextEvent 
 * because of desk accessories. If you don't patch 
 * SystemEvent then the patch will not apply to events that 
 * are sent to DAs.
 * 
 * If you want to eat the event and not pass it on to the 
 * caller then set returnValue to zero.
 *********************************************************/
pascal short MySystemEvent(EventRecord *theEvent)
{
    PatchGlobalsPtr pgPtr;
    short           returnValue;

    /* find our globals */
    pgPtr = (PatchGlobalsPtr) ((long) StartPatchCode -
      sizeof(PatchGlobals));

    /* call original GNE first */
    returnValue = (*pgPtr->pgOldSE)(theEvent);

    /* do some post-processing */
    CheckKeyCase(theEvent);

    /* return to original caller */
    return (returnValue);
}

/*********************************************************
 * CheckKeyCase:
 * If theEvent was a keyDown or autoKey event, this checks 
 * if both the shiftKey and the capsLock key were down. If 
 * so, it changes theEvent to be a lowercase letter. If not, 
 * nothing is changed.  Also, if either the optionKey or 
 * cmdKey is down then nothing is changed.
 ********************************************************/
void CheckKeyCase(EventRecord *theEvent)
{
    register long   theMods, theMessage;
    register char   theChar;

    if (theEvent->what == keyDown ||
      theEvent->what == autoKey) {
        theMods = theEvent->modifiers;
        theMods &= shiftKey | alphaLock |
          optionKey | cmdKey;
        theMods ^= shiftKey | alphaLock;
        if (!theMods) {
            theMessage = theEvent->message;
            theChar = theMessage & charCodeMask;
            if (theChar >= 'A' && theChar <= 'Z') {
                theMessage &= ~charCodeMask;
                theMessage |= theChar + 'a' - 'A';
                theEvent->message = theMessage;
            }
        }
    }
}
/*********************************************************
 * EndPatchCode:
 * Dummy proc to mark the end of the code for the patches.
 * Make sure all of your patch code is between here and 
 * StartPatchCode.
 *********************************************************/
void EndPatchCode()
{
}
 
AAPL
$100.53
Apple Inc.
+1.37
MSFT
$45.33
Microsoft Corpora
+0.22
GOOG
$586.86
Google Inc.
+4.70

MacTech Search:
Community Search:

Software Updates via MacUpdate

Chromium 36.0.1985.143 - Fast and stable...
Chromium is an open-source browser project that aims to build a safer, faster, and more stable way for all Internet users to experience the web. FreeSMUG-Free OpenSource Mac User Group build is... Read more
Macgo Blu-ray Player 2.10.6.1691 - Blu-r...
Macgo Mac Blu-ray Player can bring you the most unforgettable Blu-ray experience on your Mac. Overview Macgo Mac Blu-ray Player can satisfy just about every need you could possibly have in a Blu-ray... Read more
Apple Motion 5.1.2 - Create and customiz...
Apple Motion is designed for video editors, Motion 5 lets you customize Final Cut Pro titles, transitions, and effects. Or create your own dazzling animations in 2D or 3D space, with real-time... Read more
A Better Finder Rename 9.39 - File, phot...
A Better Finder Rename is the most complete renaming solution available on the market today. That's why, since 1996, tens of thousands of hobbyists, professionals and businesses depend on A Better... Read more
PopChar X 6.6 - Floating window shows av...
PopChar X helps you get the most out of your font collection. With its crystal-clear interface, PopChar X provides a frustration-free way to access any font's special characters. Expanded... Read more
MacUpdate Desktop 6.0.2 - Install Mac ap...
MacUpdate Desktop 6 brings seamless 1-click installs and version updates to your Mac. With a free MacUpdate account and MacUpdate Desktop 6, Mac users can now install almost any Mac app on macupdate.... Read more
PDFpenPro 6.3.2 - Advanced PDF toolkit f...
PDFpenPro allows users to edit PDF's easily. Add text, images and signatures. Fill out PDF forms. Merge or split PDF documents. Reorder and delete pages. Even correct text and edit graphics! Create... Read more
PDFpen 6.3.2 - Edit and annotate PDFs wi...
PDFpen allows users to easily edit PDF's. Add text, images and signatures. Fill out PDF forms. Merge or split PDF documents. Reorder and delete pages. Even correct text and edit graphics! Features... Read more
OS X Yosemite 10.10 DP6 - Developer Prev...
Note: This is a Developer Preview. You must be a registered Apple Mac Developer to download this update. You can also sign up for the free OS X Beta Program to download and preview public beta... Read more
Flavours 1.1.15 - Create and apply theme...
Flavours is a Mac application that allow users to create, apply and share beautifully designed themes. Classy Give your Mac a gorgeous new look by applying delicious themes! Easy Unleash your... Read more

Latest Forum Discussions

See All

Go to Bed – An Interview With Touchfight...
Touchfight Games is an exciting new indie studio that was co-formed between game journalist and author Nathan Meunier, artist Leonard Kenyon, and programmer Jon Kenyon. Their debut game Go To Bed will be released this fall, and with all the... | Read more »
Assault Vector Review
Assault Vector Review By Rob Thomas on August 19th, 2014 Our Rating: :: SIMPLE STRATEGYUniversal App - Designed for iPhone and iPad While this hex-based warp ride is fine in theory, Assault Vector is a bit too simple to be... | Read more »
Tom Hanks’ Hanx Writer has Taken the App...
Tom Hanks’ Hanx Writer has Taken the App Store by Storm Posted by Jessica Fisher on August 19th, 2014 [ permalink ] iPad Only App - Designed for the iPad | Read more »
It Came from Canada: Galaxy Dash
With its use of well-established tropes like endless flying and sci-fi space shooting, the upcoming Galaxy Dash: Race to the Outer Run most likely won’t confound expectations. However, with its robust amount of opportunities for fun player... | Read more »
PromptSmart Review
PromptSmart Review By Jennifer Allen on August 19th, 2014 Our Rating: :: SNAPPY SPEECHGIVINGUniversal App - Designed for iPhone and iPad Offering you a teleprompter in your pocket, PromptSmart is immensely helpful for those who do... | Read more »
The Firm (Games)
The Firm 1 Device: iOS iPhone Category: Games Price: $.99, Version: 1 (iTunes) Description: RELEASE SPECIAL SALE 50%! Welcome to THE FIRM, Your profil retained our full consideration and we have the pleasure to inform you that your... | Read more »
They Need To Be Fed 3 (Games)
They Need To Be Fed 3 1.0.0 Device: iOS Universal Category: Games Price: $1.99, Version: 1.0.0 (iTunes) Description: 360 degree gravity platforming is back! Run, jump, collect, avoid enemies and feed yourself to the monster at the... | Read more »
Who Wore it Best? Phantom Flower vs. And...
Who Wore it Best? takes a break from all the bloodshed to check out two decidedly tranquil and nature-loving puzzles games: Phantom Flower and And Then it Rained. [ Who Wore it Best? Phantom Flower vs. And Then it Rained is a post from... | Read more »
Sentinel 4: Dark Star Review
Sentinel 4: Dark Star Review By Jennifer Allen on August 19th, 2014 Our Rating: :: STURDY TOWER DEFENSEUniversal App - Designed for iPhone and iPad After 4 years, the latest entry to the Sentinel series is here. It’s not quite as... | Read more »
TrashEver2 - You can move your selected...
TrashEver2 - You can move your selected notes to trash box. 1.0.0 Device: iOS Universal Category: Productivity Price: $.99, Version: 1.0.0 (iTunes) Description: TrashEver is an application only for throwing away the note of Evernote... | Read more »

Price Scanner via MacPrices.net

Razer Taipan Mouse For Gamers And Non-Gamers...
If you’re a serious gamer on either Mac or Windows PCs, a serious gaming mouse is a necessity for first-tier performance. However, even if like me you’re not much of a gamer, there’s still a strong... Read more
15-inch 2.2GHz MacBook Pro on sale for $1899,...
Adorama has the new 15″ 2.2GHz Retina MacBook Pro on sale for $1899 including free shipping plus NY & NJ sales tax only. Their price is $100 off MSRP, and it’s the lowest price available for this... Read more
Mid-Size Tablet Shootout Posted: iPad mini wi...
I ‘m curious about how many iPads Apple is actually selling these days. It’s been widely rumored and anticipated that new models with A8 SoCs, 2 GB of RAM, 8 megapixel cameras, and fingerprint... Read more
The 15 Biggest iPad Air Problems And How To A...
What’s this? Fifteen “biggest” problems with the iPad Air? Does that mean there are a lot of smaller problems as well? Say it isn’t so! My old iPad 2 has manifested no hardware problems in three... Read more
TYLT Syncable-Duo, 2-in-1 USB Cable With Appl...
TYLT has introduced the Syncable-Duo, a universal cable solution for charging and syncing data to smartphones and tablets. The Syncable-Duo eliminates the need for multiple cables by incorporating... Read more
Save up to $140 off MSRP with Apple refurbish...
Apple is offering Certified Refurbished iPad Airs for up to $140 off MSRP. Apple’s one-year warranty is included with each model, and shipping is free. Stock tends to come and go with some of these... Read more
2.5GHz Mac mini on sale for $549, save $50
B&H Photo has the 2.5GHz Mac mini on sale for $549.99 including free shipping. That’s $50 off MSRP, and B&H will also include a free copy of Parallels Desktop software. NY sales tax only. Read more
Chromebooks Are Actually Sometimes A Better C...
Mac360′s Kate MacKenzie notes that when Steve Jobs announced the post-PC era, he was assuming that the dominant post-PC device would be the iPad he was unveiling, or another Apple product like the... Read more
Files-Finder Edition 1.0 app Brings Finder/Ex...
Appsicum’s Files Finder App is designed for iPad users who miss the functionality of the OS X Finder and Windows’ Explorer on their tablet. Files-Finder Edition App is a complete tool having rich... Read more
Apple and Samsung Both Face Growth Declines I...
As the tablet PC market matures and growth slows, the dominance of market leaders Apple and Samsung is also weakening, according to the latest NPD DisplaySearch Monthly Tablet PC Panel Shipment... Read more

Jobs Board

Project Manager / Business Analyst, WW *Appl...
…a senior project manager / business analyst to work within our Worldwide Apple Fulfillment Operations and the Business Process Re-engineering team. This role will work Read more
Position Opening at *Apple* - Apple (United...
**Job Summary** As businesses discover the power of Apple computers and mobile devices, it's your job - as a Solutions Engineer - to show them how to introduce these Read more
Position Opening at *Apple* - Apple (United...
**Job Summary** As more and more people discover Apple , they visit our stores seeking ways to incorporate our products into their lives. It's your job, as a Store Read more
Position Opening at *Apple* - Apple (United...
**Job Summary** Being a Business Manager at an Apple Store means you're the catalyst for businesses to discover and leverage the power, ease, and flexibility of Apple Read more
Project Manager / Business Analyst, WW *Appl...
…a senior project manager / business analyst to work within our Worldwide Apple Fulfillment Operations and the Business Process Re-engineering team. This role will work Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.