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) .
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
-> Break at 0x00053068: fioFormatV Task: 0xfff9c20 (t1)
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 ()
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..*
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
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().
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
value = 0 = 0x0
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.
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).