Introduction to the SPARC Architecture

The Scalable Processor Architecture (SPARC) is the most widely deployed and best-supported architecture upon which Solaris runs. It was originally developed by Sun Microsystems, but has since become an open standard. The two initial versions of the architecture (v7 and v8) were 32-bit, while the latest version (v9) is 64-bit. SPARC v9 processors can run 64-bit applications as well as 32-bit applications in a legacy fallback mode.

The UltraSPARC processors from Sun Microsystems are SPARC v9 and capable of running 64-bit applications, while virtually all other CPUs from Sun are SPARC v7 or v8s, and run applications only in 32-bit mode. Solaris 7, 8, and 9 all support 64-bit kernels and can run 64-bit user-mode applications; however, the majority of user -mode binaries shipped by Sun are 32-bit.

The SPARC processor has 32 general-purpose registers that are usable at any time. Some have specific purposes, while others are allocated at the discretion of the compiler or programmer. These 32 registers can be divided into four specific categories: global, local, input, and output registers.

The SPARC architecture is big-endian in nature, meaning that integers and pointers are represented in memory with the most significant byte first. The instruction set is of fixed length, all instructions being 4 bytes long. All instructions are aligned to a 4-byte boundary, and any attempt to execute code at a misaligned address will result in a BUS error. Similarly, any attempts to read from or write to misaligned addresses will result in BUS errors and cause programs to crash.

Registers and Register Windows

SPARC CPUs have a variable number of total registers, but these are divided into a fixed number of register windows. A register window is a set of registers usable by a certain function. The current register window pointer is incremented or decremented by the save and restore instructions, which are typically executed at the beginning and end of a function.

The save instruction results in the current register window being saved, and a new set of registers being allocated, while the restore instruction discards the current register window and restores the previously saved one. The save instruction is also used to reserve stack space for local variables , while the restore function releases local stack space.

The global registers ( %g0 “%g7 ) are unaffected by either function calls or the save or restore instructions. The first global register, %g0 , always has a value of zero. Any writes to it are discarded, and any copies from it result in the destination being set to zero. The remaining seven global registers have various purposes, as described in Table 10.1.

Table 10.1: Global Registers and Purposes

Register

PURPOSE

%g0

Always zero

%g1

Temporary storage

%g2

Global variable 1

%g3

Global variable 2

%g4

Global variable 3

%g5

Reserved

%g6

Reserved

%g7

Reserved

The local registers ( %l0%l7 ) are local to one specific function as their name suggests. They are saved and restored as part of register windows. The local registers have no specific purpose, and can be used by the compiler for any purpose. They are preserved for every function.

When a save instruction is executed, the output registers ( %o0 “%o7 ) overwrite the input registers ( %i0 “%i7 ). Upon a restore instruction, the reverse occurs, and the input registers overwrite the output registers. A save instruction preserves the previous function's input registers as part of a register window.

The first six input registers ( %i0 “%i5 ) are incoming function arguments. These are passed to a function as %o0 to %o5 , and when a save is executed they become %i0 to %i5 . In the case, where a function requires more than six arguments, the additional arguments are passed on the stack. The return value from a function is stored in %i0 , and is transferred to %o0 upon restore .

The %o6 register is a synonym for the stack pointer %sp , while %i6 is the frame pointer %fp . The save instruction preserves the stack pointer from the previous function as the frame pointer as would be expected, and restore returns the saved stack pointer to its original place.

The two remaining general purpose registers not mentioned thus far, %o7 and %i7 , are used to store the return address. Upon a call instruction, the return address is stored in %o7 . When a save instruction is executed, this value is of course transferred to %i7 , where it remains until a return and restore are executed. After the value is transferred to the input register, %o7 becomes available for use as a general purpose register. A summary of input and output register purposes is listed in Table 10.2.

Table 10.2: Register Names and Purposes

Register

PURPOSE

%i0

First incoming function argument, return value

%i1 “%i5

Second through sixth incoming function arguments

%i6

Frame pointer (saved stack pointer)

%i7

Return address

%o0

First outgoing function argument, return value from called function

%o1 “%o5

Second though sixth outgoing function arguments

%o6

Stack pointer

%o7

Contains return address immediately after call, otherwise general purpose

The effects of save and restore are summarized in Tables 10.3 and 10.4 as well, for convenience.

Table 10.3: Effects of a save Instruction

1. Local registers (%l0 “%l7) are saved as part of a register window.

2. Input registers (%i0 “%i7) are saved as part of a register window.

3. Output registers (%o0 “%o7) become the input registers (%i0 “%i7).

4. A specified amount of stack space is reserved.

Table 10.4: Effects of a restore Instruction

1. Input registers become output registers.

2. Original input registers are restored from a saved register window.

3. Original local registers are restored from a saved register window.

4. As a result of step one, the %sp (%o6) becomes %fp (%i6) releasing local stack space.

For leaf functions (those that do not call any other functions), the compiler may create code that does not execute save or restore . The overhead of these operations is avoided, but input or local registers cannot be overwritten, and arguments must be accessed in the output registers.

Any given SPARC CPU has a fixed number of register windows. While available, these are used to store the saved registers. When available register windows run out, the oldest register window is flushed to the stack. Each save instruction reserves a minimum of 64 bytes of stack space to allow for local and input registers to be stored on the stack if needed. A context switch, or most traps or interrupts, will result in all register windows being flushed to the stack.

The Delay Slot

Like several other architectures, SPARC makes use of a delay slot on branches, calls, or jumps . There are two registers used to specify control flow; the register %pc is the program counter and points to the current instruction, while %npc points to the next instruction to be executed. When a branch or call is taken, the destination address is loaded into %npc rather than %pc . This results in the instruction following the branch/call being executed before flow is redirected to the destination address.

 0x10004:     CMP %o0, 0 0x10008:     BE 0x20000 0x1000C:     ADD %o1, 1, %o1 0x10010:     MOV 0x10, %o1 

In this example, if %o0 holds the value zero, the branch at 0x10008 will be taken. However, before the branch is taken, the instruction at 0x1000c is executed. If the branch at 0x10008 is not taken, the instruction at 0x1000c is still executed, and execution flow continues at 0x10010 . If a branch is annulled, such as BE, A address , then the instruction in the delay slot is executed only if the branch taken. More factors complicate execution flow on SPARC; however, you do not necessarily need to fully understand them to write exploits.

Synthetic Instructions

Many instructions on SPARC are composites of other instructions, or aliases for other instructions. Because all instructions are 4 bytes long, it takes two instructions to load an arbitrary 32-bit value into any register. More interesting, both call and ret are synthetic instructions. The call instruction is more correctly jmpl address, %o7 . The jmpl instruction is a linked jump, which stores the value of the current instruction pointer in the destination operand. In the case of call the destination operand is the register %o7 . The ret instruction is simply jmpl %i7+8, %g0 , which goes back to the saved return address. The value of the program counter is discarded to the %g0 register, which is always zero.

Leaf functions use a different synthetic instruction, retl , to return. Since they do not execute save or restore , the return address is in %o7 , and as a result retl is an alias for jmpl %o7+8, %g0 .



The Shellcoder's Handbook. Discovering and Exploiting Security
Hacking Ubuntu: Serious Hacks Mods and Customizations (ExtremeTech)
ISBN: N/A
EAN: 2147483647
Year: 2003
Pages: 198
Authors: Neal Krawetz

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net