BigAdmin System Administration Portal
Feature Article
Print-friendly VersionPrint-friendly Version

Debugging on the AMD64 Platform: Finding Argument Values

Poonam Bajaj, July 2008

Introduction

This article discusses the parameter passing convention used on the AMD64 platform, why this convention is used, and how it can make debugging and finding the values of arguments passed during function calls very difficult. We look at the register usage and calling conventions on the Solaris OS, Linux, and Microsoft Windows, and in Java technology. An example workaround is provided for AMD64 architecture on Linux.

This article covers the following topics:

Before going into the debugging details, let's have a look at the AMD64 architecture and how it is different from the 32-bit x86 architecture.

AMD64 Architecture

The AMD64 architecture has sixteen 64-bit general-purpose registers: RAX, RBX, RCX, RDX, RBP, RSI, RDI, RSP, R8, R9, R10, R11, R12, R13, R14, and R15. Compared to the x86 architecture, the AMD64 architecture has eight new general-purpose registers.

The RAX, RBX, RCX, RDX, RBP, RSI, RDI, and RSP registers are used by both 32-bit and 64-bit binaries. However, in 32-bit mode, only the lowest 32 bits of these registers are accessible by 32-bit binaries. In the x86 architecture, these registers are EAX, EBX, ECX, EDX, EBP, ESI, EDI, and ESP.

The AMD64 architecture also provides sixteen 128-bit XMM registers and eight x87 floating point registers.

Argument Passing in x86 Architecture

Let's first see the stack organization and how parameters are passed in a 32-bit x86 architecture. In the x86 architecture, parameters are passed on the stack. When a function calls another function, first all the parameters are pushed, one after another from right to left, onto the stack. Then the address of the instruction after the "call" instruction, which is the return address for the call, is pushed. At this point, the caller loses control and the callee gets control.

The called function saves the EBP value on the stack, and the EBP register then contains the ESP value (which is marked as 0(%ebp) in Table 1). Then the called function can grow the stack and use that for storing its locals.

Table 1: Stack Values at Function Call
Stack Address
Stack Value
16(%ebp)
Third function parameter
12(%ebp)
Second function parameter
8(%ebp)
First function parameter
4(%ebp)
Old %EIP (the function's "return address")
0(%ebp)
Old %EBP (previous function's base pointer)
-4(%ebp)
First local variable
-8(%ebp)
Second local variable
-12(%ebp)
Third local variable
 

In the previous stack depiction, the stack grows downwards.

Using Registers for Passing Arguments on the AMD64 Platform

Since the AMD64 architecture has eight new general-purpose registers, as a performance enhancement, it was decided to use registers, wherever possible, to pass arguments to functions.

The AMD64 architecture defines a number of classes to classify arguments, such as INTEGER, SSE, SSEUP, MEMORY, and so on. Arguments of types (signed and unsigned) _Bool, char, short, int, long, and long long, as well as pointers, are in the INTEGER class; this is the kind of arguments we are discussing here.

INTEGER class arguments are passed in registers. Once arguments are classified, the registers get assigned (in left-to-right order) for passing. For the INTEGER class, the next available register of the sequence %rdi, %rsi, %rdx, %rcx, %r8, and %r9 is used.

Table 2: Register Usage for the Solaris OS and Linux
Register
Usage
Preserved Across Function Calls?
%rax
Temporary register; with variable arguments, passes information about the number of SSE registers used; first return register
No
%rbx
Callee-saved register; optionally used as base pointer
Yes
%rcx
Used to pass fourth integer argument to functions
No
%rdx
Used to pass third argument to functions; second return register
No
%rsp
Stack pointer
Yes
%rbp
Callee-saved register; optionally used as frame pointer
Yes
%rsi
Used to pass second argument to functions
No
%rdi
Used to pass first argument to functions
No
%r8
Used to pass fifth argument to functions
No
%r9
Used to pass sixth argument to functions
No
%r10
Temporary register; used for passing a function's static chain pointer
No
%r11
Temporary register
No
%r12 through r15
Callee-saved registers
Yes
 

The first six arguments are passed in registers and the rest on the stack.

Table 3: Registers Used for Passing Arguments on Solaris OS and Linux
Argument
Register Used
arg0
%rdi
arg1
%rsi
arg2
%rdx
arg3
%rcx
arg4
%r8
arg5
%r9
argN
8*(N-4)(%rbp)
 

Here is additional architecture-specific information about register usage.

Register usage for Microsoft Windows:

On the Microsoft Windows AMD64 platform, there are only four INTEGER argument registers: RCX, RDX, R8, and R9.

Register usage by Java Native Interface (JNI) methods:

In Java technology, in an attempt to do less shuffling of arguments, the calling convention of JNI methods is a shifted version of the C convention.

On the Solaris OS and Linux, the argument register order while calling JNI methods is RSI, RDX, RCX, R8, R9, and RDI.

On Microsoft Windows, the argument register order while calling JNI is RDX, R8, R9, RDI, RSI, and RCX.

Now, these argument registers are not callee-saved and are not guaranteed to have the argument values when we look at them in the called function. This can make it very, very difficult to get the argument values.

Workaround

Here is how I got to the argument values while working on a core file from a 1.5.0_14 HotSpot Server Java Virtual Machine (JVM) crash on a Linux machine built on the AMD64 platform. The stack trace of the crash is as follows:

(gdb) bt
#0  0x00000031b8b2e21d in raise ()
#1  0x00000031b8b2fa1e in abort ()
#2  0x0000002a95ad5372 in os::abort ()
#3  0x0000002a95bcfaa0 in VMError::report_and_die ()
#4  0x0000002a9584ea66 in report_fatal ()
#5  0x0000002a95ac41d8 in 
nmethod::continuation_for_implicit_exception ()
#6  0x0000002a95b41499 in 
SharedRuntime::continuation_for_implicit_exception ()
#7  0x0000002a95ad9726 in JVM_handle_linux_signal ()
#8  0x0000002a95ad716e in signalHandler ()
#9  <signal handler called>
#10 0x0000002a99d01d2c in ?? ()
#11 0x0000000047b85b68 in ?? ()
#12 0x0000000047b85b28 in ?? ()
#13 0x0000002abdb30b7a in ?? ()
#14 0x0000000047b85b68 in ?? ()
#15 0x0000002a9f800b28 in ?? ()
#16 0x0000000000000000 in ?? ()

For this crash, I wanted to see what code was executed that caused this fatal error. To do that, I wanted to get the nmethod that contains the server virtual machine compiled code being executed. The signature of frame 5 function is as follows:

address nmethod::continuation_for_implicit_exception(address pc)

In frame 5, the first argument is nmethod and the second argument is the program counter.

Looking at the instructions of the caller of frame 5, I saw the following:

(gdb) x/10i 0x2a95b41499-20
0x2a95b41485 <_ZN13SharedRuntime35continuation_for_implicit_exception
EP10JavaThreadPhNS_21ImplicitExceptionKindE+837>:  cmp    %rcx,%r10
0x2a95b41488 <_ZN13SharedRuntime35continuation_for_implicit_exception
EP10JavaThreadPhNS_21ImplicitExceptionKindE+840>:
    jl     0x2a95b41373 <_ZN13SharedRuntime35continuation_for_implicit_exception
EP10JavaThreadPhNS_21ImplicitExceptionKindE+563>
0x2a95b4148e <_ZN13SharedRuntime35continuation_for_implicit_exception
EP10JavaThreadPhNS_21ImplicitExceptionKindE+846>:  :mov    %r13,%rdi
0x2a95b41491 <_ZN13SharedRuntime35continuation_for_implicit_exception
EP10JavaThreadPhNS_21ImplicitExceptionKindE+849>:  mov    %rbx,%rsi
0x2a95b41494 <_ZN13SharedRuntime35continuation_for_implicit_exception
EP10JavaThreadPhNS_21ImplicitExceptionKindE+852>:
    callq  0x2a95ac4160 <_ZN7nmethod35continuation_for_implicit_exceptionEPh>
0x2a95b41499 <_ZN13SharedRuntime35continuation_for_implicit_exception
EP10JavaThreadPhNS_21ImplicitExceptionKindE+857>:  test   %rax,%rax

Here, note that before the call instruction, the two arguments were moved into the RDI and RSI registers from the R13 and RBX registers, respectively. Looking at the registers of frame 6, we see the following:

(gdb) info registers
rax            0x0      0
rbx            0x2a99d01d2c     182969179436
rcx            0xffffffffffffffff       -1
rdx            0x6      6
rsi            0x7800   30720
rdi            0x7767   30567
rbp            0x47b854a0       0x47b854a0
rsp            0x47b85470       0x47b85470
r8             0x6      6
r9             0x7800   30720
r10            0x8      8
r11            0x202    514
r12            0x0      0
r13            0x2a99d01ad0     182969178832
r14            0x2a99d01d2c     182969179436
r15            0x2ac9081190     183761375632
rip            0x2a95b41499     0x2a95b41499
<SharedRuntime::continuation_for_implicit_exception(JavaThread*,
unsigned char*,SharedRuntime::ImplicitExceptionKind)+857>

eflags         0x202    [ IF ]
.......

Now, looking at the value in R13, we see the following:

(gdb) x/8x 0x2a99d01ad0
0x2a99d01ad0:   0x0000002a95dd2770      0x0000002a95c12055
0x2a99d01ae0:   0x00000108000013d8      0x000001d0000000c0
0x2a99d01af0:   0x0000121800000a38      0x0000000c00000038
0x2a99d01b00:   0x00000000fffffffe      0x0000002acb2a6190

And, we also see this:

(gdb) x/6x 0x0000002a95dd2770
0x2a95dd2770 <_ZTV7nmethod+16>: 0x0000002a95797060
0x0000002a95ac4410
0x2a95dd2780 <_ZTV7nmethod+32>: 0x0000002a95797080
0x0000002a95797090
0x2a95dd2790 <_ZTV7nmethod+48>: 0x0000002a957970a0
0x0000002a957970b0

This confirms that it is nmethod.

A rip of frame 10 confirmed that RBX contains the second argument program counter:

(gdb) frame 10
#10 0x0000002a99d01d2c in ?? ()
(gdb) info registers
.....
rip            0x2a99d01d2c     0x2a99d01d2c
.....

In this case, I was lucky to get the arguments from the callee-saved registers that contained the argument values before these were moved to the argument registers. But if these registers (R13 and RBX, in this case) are used by the called function, then that function would save them on the stack before using these registers.

In such a case, from the called function's instructions, we should find where those callee-saved registers are saved on the stack, and then the arguments can be accessed using the RBP pointer plus the required offset.

Summary

So, here's a summary of the steps followed to find argument values on AMD64 platform:

  1. Identify the frame whose argument values you are interested in. Let's say frame n.
  2. Look at the disassembly of the caller of frame n, say frame n+1.
  3. From the caller's disassembly, find out where the argument values came from in the argument registers (from the stack or from some callee-saved registers). For example, in the previous example, values from R13 and RBX were moved into argument registers RDI and RSI, respectively.
  4. If the arguments were moved from callee-saved registers, then in the caller frame (n+1), see whether those registers have valid argument values. Otherwise, examine the disassembly of functions above caller frame n+1 (that is, n, n-1, n-2, and so on) in the stack to find where those callee-saved registers were saved on the stack.

For More Information

Here are additional resources:


Comments (latest comments first)

Discuss and comment on this resource in the BigAdmin Wiki

Unless otherwise licensed, code in all technical manuals herein (including articles, FAQs, samples) is provided under this License.


BigAdmin