[comp.sys.amiga.tech] Code generation

peterk@cbmger.UUCP (Peter Kittel GERMANY) (10/15/90)

Again that old theme '(self) modifying code': When I think about it,
there HAS to be a clean way. How else could a debugger or a machine
language monitor work? I must be able to poke some bytes in the
memory to other values and then take this as code or data just as
I want, without LoadSeg()ing this everytime.

Simply put: How does LoadSeg() do it????

-- 
Best regards, Dr. Peter Kittel  // E-Mail to  \\  Only my personal opinions... 
Commodore Frankfurt, Germany  \X/ {uunet|pyramid|rutgers}!cbmvax!cbmger!peterk

mcmahan@netcom.UUCP (Dave Mc Mahan) (10/16/90)

 In a previous article, peterk@cbmger.UUCP (Peter Kittel GERMANY) writes:
>Again that old theme '(self) modifying code': When I think about it,
>there HAS to be a clean way. How else could a debugger or a machine
>language monitor work? I must be able to poke some bytes in the
>memory to other values and then take this as code or data just as
>I want, without LoadSeg()ing this everytime.

Debuggers on modern (68000) CPUs generally use one of two methods for
tracing a program.  The first will ALWAYS work, but is slow.  The second
runs at full speed (until the desired breakpoint is hit) but requires the
code being debugged to be in RAM.

1.  The debugger operates as kind of a micro-OperatingSystem.  When you
    tell it to go, it sets the TRACE bit of the 68000.  This is a hardware
    debug mode that Motorola thoughfully included on all CPUs.  Before every
    instruction is executed, the CPU traps back to a special interrupt
    vector (the TRACE vector) and enters Supervisor mode so that no other
    TRACE interrupts will occur.  The TRACE vector points to the start of
    the debugger routine that does whatever the debugger wants to do, usually
    decode the instruction into the assembly mneumonic and dump all the
    registers for the user to see.  When the debugger has finished whatever
    it intended to do, it does an RTE instruction which causes the target
    opcode to be executed just like normal.  When the next instruction after
    that is about to be executed, the TRACE interrupt is again asserted by
    the CPU and the whole process repeats.  This method works even with code
    in ROM (and with a ROMed debugger, if you have one) and only requires
    some RAM to manage the debugger stack and local variables.  Since it
    interrupts every instruction, the execution time is noticeably affected.

2.  The second method used works only with debug code in RAM.  The debugger is
    instructed by the user to replace the instruction at the desired address
    with an illegal instruction (usually, the ILLEGAL instruction itself!  This
    is a real op-code, haveing a value of 0x8AFC.  Look it up!)  The debugger
    then replaces the location in the interrupt vector table where the
    ILLEGAL instruction would vector to and provides it's own internal vector
    for processing the breakpoint (The exception vector table must be in RAM
    also for this to work).  The debugger then starts execution at full speed
    wherever the programmer tells it to.  When it hits the ILLEGAL instruction,
    the CPU vectors to the proper spot in the debugger to handle the breakpoint.
    This method allows you to run at full speed, but has the side effect of
    never stopping if you put your breakpoint at the wrong place and that code
    doesn't get executed.  Generally, the programmer places two breakpoints
    so that he can trap at the desired point when it hits, but can always force
    the program to hit the other one if he screwed up and didn't put it in the
    right spot or if the program is doing something he didn't expect (which is
    why he is probably debugging with a monitor in the first place).


>Simply put: How does LoadSeg() do it????

Do what?  I have never used LoadSeg(), but my book says that it just loads
a module into memory and links the scattered segments together.  All it does
is load.


>Best regards, Dr. Peter Kittel  

   -dave

peterk@cbmger.UUCP (Peter Kittel GERMANY) (10/16/90)

In article <14827@netcom.UUCP> mcmahan@netcom.UUCP (Dave Mc Mahan) writes:
> In a previous article, peterk@cbmger.UUCP (Peter Kittel GERMANY) writes:
>>Again that old theme '(self) modifying code': When I think about it,
>>there HAS to be a clean way. How else could a debugger or a machine
>
>2.  The second method used works only with debug code in RAM.  The debugger is
>    instructed by the user to replace the instruction at the desired address
>    with an illegal instruction (usually, the ILLEGAL instruction itself!  This
>    is a real op-code, haveing a value of 0x8AFC.  Look it up!)

Stop. Here you again poke simply two bytes into RAM and hope they will
get executed. But in the days of (separate) data and instruction caches
you can't be sure that the CPU will see this!

So again, the only official method to bring executable code into
memory and execute it there is to LoadSeg() it. So this function
must be the BIG exception that is capable of some magic trick to
circumvent this cache problem under any circumstance.
Simple question: How?
2nd question: If we know this trick, would it be also applyable
for our own hacks/monitors/code generators?

-- 
Best regards, Dr. Peter Kittel  // E-Mail to  \\  Only my personal opinions... 
Commodore Frankfurt, Germany  \X/ {uunet|pyramid|rutgers}!cbmvax!cbmger!peterk

DXB132@psuvm.psu.edu (10/17/90)

There are functions in the 2.0 Exec for flushing the cache(s) if they exist.
You simply call one of them after creating or modifying code (in this example,
after poking $4AFC). LoadSeg does this. But you can't use LoadSeg for everythin
g. :-)

-- Dan Babcock

p554mve@mpirbn.mpifr-bonn.mpg.de (Michael van Elst) (10/18/90)

In article <14827@netcom.UUCP> mcmahan@netcom.UUCP (Dave Mc Mahan) writes:
>Do what?  I have never used LoadSeg(), but my book says that it just loads
>a module into memory and links the scattered segments together.  All it does
>is load.

The operation performed by LoadSeg() is strictly 'self-modifying' code.
(At least if you reuse memory for a new program). Self-modifying code
leads to problems when you have a non-transparent cache like in the
68020/68030 where code could be changed in memory but old code could
be still executed out of the cache. The Kickstart 1.3 LoadSeg() does
nothing to prevent this but the sheer length of the LoadSeg routine
will force the obsolete contents of the cache to be flushed (== replaced
by the LoadSeg code). I think Kickstart 2.0 does better things since
the cache is supported by new Exec system calls, this allows for larger
caches where you can't trust that executing code of the size of LoadSeg()
will invalidate old cache entries.

Regards,
-- 
Michael van Elst
UUCP:     universe!local-cluster!milky-way!sol!earth!uunet!unido!mpirbn!p554mve
Internet: p554mve@mpirbn.mpifr-bonn.mpg.de
                                "A potential Snark may lurk in every tree."

mcmahan@netcom.UUCP (Dave Mc Mahan) (10/18/90)

 In a previous article, peterk@cbmger.UUCP (Peter Kittel GERMANY) writes:
>In article <14827@netcom.UUCP> mcmahan@netcom.UUCP (Dave Mc Mahan) writes:
>> In a previous article, peterk@cbmger.UUCP (Peter Kittel GERMANY) writes:
>>>Again that old theme '(self) modifying code': When I think about it,
>>>there HAS to be a clean way. How else could a debugger or a machine
>>
>>2.  The second method used works only with debug code in RAM.  The debugger is
>>    instructed by the user to replace the instruction at the desired address
>>    with an illegal instruction (usually, the ILLEGAL instruction itself!  This
>>    is a real op-code, haveing a value of 0x8AFC.  Look it up!)
>
>Stop. Here you again poke simply two bytes into RAM and hope they will
>get executed. But in the days of (separate) data and instruction caches
>you can't be sure that the CPU will see this!

Yes, it is possible for your program to write to memory and have the write stay
in the cache instead of actually getting flushed into memory, or to have the
instruction cache already loaded with the instruction location you replaced and
have it execute from the cache version rather than the modified version.  Now
that cache machines are upon us, this fact has to be remembered by the person
writing the debugger.  The only method I can think of that would work is to
write the desired (illegal) instruction and then flush the cache into memory
to guarantee that it exists, and then to dump the entire instruction cache and
force the CPU to re-read data from main memory when it is about to execute
an instruction.  Note that this does not mean the CPU has to read data from
main memory all the time, just the first time, as it will read in the modified
instruction code and store it in the cache.  The other option is to disable
caching entirely, and force the CPU to read each instruction from memory all
the time.  This method may not work if the program being debugged decides to
enable the cache as part of the sequence of instructions it is to perform.
You are correct in assuming that LoadSeg() MUST be able to write bytes of
data into memory and then execute them as instructions, but I think that it
just flushes the data cache when it is finished writing the segment being
loaded, and then marks the entire instruction cache as being 'no good' so
that all instructions are re-read when they are initially required by the
CPU execution unit.  Although Commodore only officially supports LoadSeg()
as the means of loading data into memory, programmers who write debuggers
must bend the rules slightly (and be willing to support future modifications)
to properly do certain things on a CPU with caches.

>So again, the only official method to bring executable code into
>memory and execute it there is to LoadSeg() it. So this function
>must be the BIG exception that is capable of some magic trick to
>circumvent this cache problem under any circumstance.
>Simple question: How?

It just flushes the data cache and marks the instruction cache as being
empty when it is finished as described above.  Please remember that other
functions that pass messages back and forth between seperate CPUs working
off the same main memory must also ensure that any writing they do that
needs to be shared is also flushed.  Message passing within a one-CPU system
won't have this problem, since such a message is always treated as data.


>2nd question: If we know this trick, would it be also applyable
>for our own hacks/monitors/code generators?

I'm not sure how applicable the technique is for hacks or code generators, but
for a debug monitor that allows placing breakpoints, I can think of no other
way to perform the job except to flush caches.


>Best regards, Dr. Peter Kittel

     -dave

dave@unislc.uucp (Dave Martin) (10/23/90)

From article <515@cbmger.UUCP>, by peterk@cbmger.UUCP (Peter Kittel GERMANY):
> Stop. Here you again poke simply two bytes into RAM and hope they will
> get executed. But in the days of (separate) data and instruction caches
> you can't be sure that the CPU will see this!

Invalidate the caches after modifying the instruction stream.