Quit Bugging Me: ABI

What's in an ABI?  An Application Binary Interface is a lot like a
API – Applications Programming Interface, except instead of just
telling you how to make a call, what parameters to provide, and what
returns and errnos to expect, an ABI also tells you something about how
the interfaces work.  Understanding an ABI for a CPU means you
understand how the registers are used, what they're for, and what they
mean under different contexts.

Among the things ABIs give you is
how your CPU formats memory (the stack) when it makes a call to a
routine. An easy way to take a look at an ABI is to set a break point
on a routine, and look at what the stack contains.  This is on a
PowerPC target.  First I'll spawn a task, then extract the current
information about the task – stack pointer, stack base, etc, using
target-side debug routines.

The PowerPC ABI gives
specifications for how and when routines use the stack to store
values.  I'll use some knowledge of the ABI and values from the
target-side debug routines to show you a simple ABI trick.

I've spawned:  printf("this is a test. %d %d %d %d %d %d \n", 1, 2, 3, 4, 5, 6) .

I've
set break a break point on "fioFormatV" for this demonstration, so I
can show you how printf was called.  When printf() calls fioFormatV()
it has to store some of it's work on the stack.  It does this so
fioFormatV() can do work without destroying the data printf is supposed
to handle.  Let's look at some task information and the contents of the
stack.  I've removed some output we're not concerned with for this
example.

-> Break at 0x00053068: fioFormatV          Task: 0xfff9c20 (t1)

-> ti

  NAME         ENTRY       TID    PRI   STATUS      PC       SP     ERRNO  DELAY
———-  ———— ——– — ———- ——– ——– ——- —–
t1          printf        fff9c20 100 STOP          53068  fff9b80       0     0

task stack: base 0xfff9c20   end 0xfff4e00   size 20000  high 160    margin 19840

r0         = 0x0002cae4   sp         = 0x0fff9b80   r2         = 0x00000000
r3         = 0x0fff4dbc   r4         = 0x0fff9ba8   r5         = 0x000545a4
r6         = 0x00000001   r7         = 0x00000004   r8         = 0x00000005
r9         = 0x00000000   r10        = 0x00000000   r11        = 0x0fff9b88
r12        = 0x0000000d   r13        = 0x00000000   r14        = 0x00000000
r15        = 0x00000000   r16        = 0x00000000   r17        = 0x00000000
r18        = 0x00000000   r19        = 0x00000000   r20        = 0x00000000
r21        = 0x00000000   r22        = 0x00000000   r23        = 0x00000000
r24        = 0x00000000   r25        = 0x00000000   r26        = 0x00000000
r27        = 0x00000000   r28        = 0x00000000   r29        = 0x00000000
r30        = 0x00054474   r31        = 0x00000000   msr        = 0x02029230
lr         = 0x000544d8   ctr        = 0x00054474   pc         = 0x00053068

-> d 0xfff9b80
0x0fff9b80:  0fff9bc0 eeeeeeee 0fff4dbc 00000001  *……….M…..*
0x0fff9b90:  00000002 00000003 00000004 00000005  *…………….*
0x0fff9ba0:  00000006 00000000 01000dee 0fff9bc8  *…………….*
0x0fff9bb0:  0fff9b88 eeeeeeee eeeeeeee eeeeeeee  *…………….*
0x0fff9bc0:  0fff9be0 0002cae4 00000000 00000000  *…………….*
0x0fff9bd0:  eeeeeeee eeeeeeee 00000000 00000000  *…………….*
0x0fff9be0:  00000000 00000000 eeeeeeee eeeeeeee  *…………….*
0x0fff9bf0:  eeeeeeee eeeeeeee 0fff4dbc 00000001  *……….M…..*
0x0fff9c00:  00000002 00000003 00000004 00000005  *…………….*
0x0fff9c10:  00000006 00000000 00000000 00000000  *…………….*
0x0fff9c20:  00000000 0fff9c20

-> tt t1
0x0002cae4 vxTaskEntry  +0x5c : printf (0xfff4dbc, 0x1)
0x000544d8 printf       +0x64 : fioFormatV ()

Okay…
looking at the task trace, we see printf was called with some values. 
The first value doesn't look like our string, it looks like an
address.  This should be the address where our string is… that's what
the ABI says:

-> d 0xfff4dbc
NOTE: memory values are displayed in hexadecimal.
0x0fff4db0:                             74686973  *            this*
0x0fff4dc0:  20697320 61207465 73742e20 20256420  * is a test.  %d *
0x0fff4dd0:  25642025 64202564 20256420 25640a00  *%d %d %d %d %d..*

Yes…
that's the format string for printf.  From the task trace there's only
one additional argument. But looking at values stored on the stack –
starting at the address in the "stack pointer" address from the "ti"
task information we see some things we recognize… all in a "row"
there is the address of the string above, and the numbers 1 through 6 –
this is the arguments passed in to printf.  At the very first address,
there's an odd value – 0fff9bc0 – this address is between the stack
pointer and stack base.  It's called a back-chain: it points back to
the previous stack frame.  Following back-chains back, fff9bc0 leads to
0fff9be0,
which contains 0.  But.. we're still not back to the base of the stack
yet.  What gives?  This area – between the "0" valued back-chain and
the base of the stack, is the initial stack frame which is used to
preserve the arguments originally handed to taskSpawn().  The first
stack frame is the part of the stack used by a routine referred to as
"vxTaskEntry" in the "task trace" output.
  This previous part is where taskSpawn() holds the original values for taskRestart().

From
the PowerPC ABI, we can see that when one routine calls another,
standard stack usage is to preserve the data the caller is going to
need later on, by pushing it onto the stack.  printf is calling
fioFormatV, printf is responsible for saving some registers, so it puts
them on the stack (look up "caller-saves" and "callee-saves"). It's
easy enough for us to identify the arguments we handed to taskSpawn for
printf to use on the stack.

Looking back at the top stack frame – between fff9b80
and fff9bc0, we can change these values and affect what gets printed. 
It should print "1 2 3 4 5 6". Let's change the 6 to a 9.  After
changing the value, we'll "c" continue the execution and see what it
prints.  In that top stack frame, "00000006" is stored at 0xfff9ba0, so
we'll modify memory there:

-> m 0xfff9ba0
0x0fff9ba0:  0x0000-
0x0fff9ba2:  0x0006-0009
0x0fff9ba4:  0x0000-.

value = 0 = 0x0
-> c
value = 0 = 0x0
-> this is a test.  1 2 3 4 5 9

By changing the value printf() had saved on the stack according to the ABI, we changed the value printf actually printed out.

By
using the ABI and the contents of memory, you can see if something has
happened to corrupt the arguments given to a routine you're interested
in testing.  You can also use this kind of method to force erroneous
values into a routine for testing purposes.  Given that the ABI
specifies a routine will store values to the stack when it calls other
routines, you may need to find the first call-out performed by the
routine you are interested in (as I did above using fioFormatV, which
is called by printf).