Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

powerpc macs: exploring the firmware

9 views
Skip to first unread message

Tinkerer Atlarge

unread,
Jan 28, 2010, 7:19:45 AM1/28/10
to
Some things observed while browsing through Open Firmware boot monitor code on
a Apple Macintosh "New World" PowerPC computer:

Register usages discovered: Very important to know which cpu registers you can
use for your own probes. Also discovery of their normal usage provides a
partial outline of the computer's memory map - a vital prerequisite to more
detailed investigation.

Below are things observed when browsing while trying out "dis.of". No attempt
was made to confirm how much they conform to the ABI documented by IBM -- I
wasn't trying to be that systematic. However I noticed some of the lower level
stuff (which looks like it was compiled with C) seems to use r3 for function
args as per the ABI. Interrupts save r0-r3 and presumably restore them on rfi.

r20,r31 = forth Parameter Stack. r20 is TOS (top of stack), 0(r31) is NOS
(next on stack).
r21 used as temp NOS, avoids mem access when no need to change r31 stack depth
( r21 is often free for your own use, as is the case with r0, r3, r4, r5)
r19,r30 = forth Return Stack (similar implementation to forth paramater stack)
r26 Local variables frame pointer. Allocates/deallocates 32-byte frames.
r28 Base of fcode lookup table(s). fcode execution address = (fcode*4)(r28)
r25 Called "startvec" most executable code is located between (r25) and (r16)
r16 = 'here' = start of unallocated user mem (%r16 always updated)
r17 always ends up = r16. %r17 pseudo-reg lags behind %r16, but an "unknown
word" typed at forth prompt will bring it back in line.

Forth's %-prefixed register defs are not "live" except for the %fr (floating
point) registers which are copied on demand direct from the cpu registers. The
others only return what was dumped last time an appropriate exception
occurred. They, and the various breakpoint routines routinely listed in Open
Firmware glossaries, unfortunately to support the "client interface" (BootX,
OS X, Linux) but not the "user interface" (you, me and the forth prompt).
Thus .registers and .bp etc do not do for us what you might have been hoping.
However the 'debug' command does appear to update them as frequently as you
get the opportunity to look. Unfortunately they only work with standard colon
defs, not the code defs which show you how the registers are being used.

To update those pseudo-registers you need to generate a "program exception".
To do that outside of 'debug' you can
(a) make a silly mistake, eg type @ on an empty stack and maybe get one gratis
(b) deliberately trigger processor exception, eg with "0 execute" or "3000 @"
(c) press Ctrl-Z which is the more disruptive of these options on my eMac.
Also, the 'load' command will refresh them, no doubt in the expectation that a
"client" program (OS) is about to be launched.

A problem with the pseudo registers when you are trying to refresh them is
that they are usually being updated from the same place in the input loop
every time. Thus some registers, like r30 (return-stack pointer) could be
mistaken for a static pointer since it usually returns to the same address as
last time you looked. To make a live snapshot of the registers in action you
need azm. The simplest way is to find a code def which references the register
of interest, then use azm to intercept its control flow in such a way that the
register's current contents are dumped where they remain viewable back at the
prompt.

One problem is that you don't want to change the contents of any register
which may be in use.

One way to avoid changing registers is to make use of their existing contents.
Since we know r16 always points to free memory, we can dump any register there
with, eg: <address> azm stw r24,0(r16) , addr azm stw r25,4(r16) , addr
azm stw r26,8(r16) ... etc , then, immediately afterwards, view the snapshot
with:

here 20 dumpl ( use 'dump' if you don't have 'dumpl' )

Or, you can make an instantaneous snapshot of ALL gpr's at a single stroke
with the instruction:

<address> azm stmw r0, 0(r16)

(If you are doing it via telnet, I suggest you first type 'align' then use an
offset of 10 rather than 0 as 'here' has a tendency to creep up in the
background, zapping unallocated memory as it goes, when you are accessing open
firmware via a remote terminal)

However the main problem is that overwriting some of the instructions we are
trying to study is not only bad science, but will probably crash the computer,
which means we never get to see the results.

To do it properly, you need to write a "patch" using asm, then divert the live
code to it using azm. It is fairly simple in theory. You use asm to write a
code-def in the normal way. You copy the instruction you are going to
overwrite from the code being investigated into your new code def. You then
add your register dump instruction(s), and wind up with 'asm b address' back
to the address of the instruction immediately following the instruction you
copied

Because your code def ("the patch") ends with a "b" as opposed to "bl"
instruction, you terminate its definition with "end-code" instead of the
normal "c;"

You then go to the code about to be patched and "azm b <address>"
substituting the execution address of your own code def for <address>. You
need to be clear about the distinction between a branch instruction's address
(its own location in memory) and its target address (the address a b or bl
instruction will branch to when executed).

Then execute the patched definition as normal. There should be nothing to
indicate anything out of the ordinary has happened. If there is, you made a
potentially serious mistake and should immediately type 'reset-all' if
possible, otherwise briefly power off and restart. Assuming nothing of the
kind happened, copies of the register contents dumped by your intercept should
be viewable using dumpl in the normal way.

Don't forget to repair the altered code back to its original form before you
define anything else. If you are unable to do so (because the instruction you
were going to copy scrolled off the screen, for example), wind up your session
by typing 'reset-all' before doing anything else (apart from examining the
register contents just captured).

I started trying to write more detailed instructions using examples, but I
abandoned the attempt because I couldn't even type them in myself without
making errors. The chances of anyone trying to cope with an unfamiliar
interface, unfamiliar language and unfamiliar cpu all at the same time without
making a critical mistake is just about zero. However as you gain familiarity
and confidence through simpler usage, you will eventually acquire the know-how
and confidence to do it your own way in response to comprehending the logic of
how it is done.

Don't underestimate the importance of being able to nut through the simplest
code defs. Pay particular attention to the way the return and parameter stacks
are implemented. Hint: the cpu saves return addresses in the link register
which will be overwritten by the next bl instruction unless they are handled
appropriately. If you can't make sense of simple definitions like

' @ dasm
' r@ dasm
' r> dasm

then you will gain nothing more than the conviction that "this stuff doesn't
work" when trying to intervene in the operation of more complex ones.

It's worth persevering with, but it requires more than comprehension.
Assembler (not to mention forth) needs practice and skill at recognizing and
manipulating its symbols. It's like algebra in that respect. If you're finding
it hard, you probably skipped something you would have been better off
becoming more comfortable with first.

Here is something I wrote to list my eMac's fcode names, to take advantage of
my discovery that r28 points at a table of pointers to fcode routines. Note
the dependency on .label which is defined in "dis.of".

\ "list-fcodes"
\ should terminate with "invalid memory access" on encountering invalid
\ table entries. Gaps in name column indicate valid but concealed defs.
\ fcodes above 241 = non-standard, often vendor-specific codes
\ Try it out with (eg): %r28 list-fcodes
\
: list-fcodes ( ptr -- ) ( ptr = starting addr in table of addresses)
cr ." vector fcode xt name"
cr ." ----------------------------"
cr BEGIN
dup . 2 spaces
dup %r28 - 4 / . space \ %r28 = base of fcode vector table(s)
dup @ ['] ferror = IF \ ferror = dflt for unused table entries
" -- " type ( for first instance in run of ferrors)
BEGIN cell+ dup @ ['] ferror <> UNTIL \ ignore rest of run
cell-
ELSE
dup @ .
dup @ .label
THEN
cell+
cr exit? \ use keyboard to pause, stop or continue
UNTIL
drop
;

If you add the following extra line to the definition of .label in "dis.of",
it will unlock more of the missing labels. The few that remain with blanks in
the name column have had their labels stripped -- the firmware's ultimate
level of concealment.

\ optionally insert new first line below colon header of .label def in dis.of
dup 4 mod IF cell- THEN \ uncovers b()-word headers in (%r28) list

Cheers

Tink

0 new messages