How to get .Net function parameters from crash dump

When debugging a crashed application from a crash dump, you often see that arguments to functions in the call stack are no data. Let’s say you want to get arguments for Hoge.Program.Test(Hoge.Foo, Hoge.Bar).
Since x64 calling convention use rcx/rdx/r8/r9 for the first 4 arguments, it’s not automatically pushed to the stack before the function call. However, sometimes the copy is stored in stack as below.

Exception:

0:000> !pe
Exception object: 000000e1000068e0
Exception type:   System.Exception
Message:          dummy exception
InnerException:   <none>
StackTrace (generated):
    SP               IP               Function
    000000E17AADEF00 00007FFB516702BC Hoge!Hoge.Program.TestSub()+0x3c
    000000E17AADEF40 00007FFB51670243 Hoge!Hoge.Program.Test(Hoge.Foo, Hoge.Bar)+0x123
    000000E17AADEFB0 00007FFB516700FE Hoge!Hoge.Program.Main(System.String[])+0x6e

StackTraceString: <none>
HResult: 80131500

Callstack:

0:000> !clrstack
OS Thread Id: 0x1bd4 (0)
        Child SP               IP Call Site
000000e17aadee18 00007ffbc0718b9c [HelperMethodFrame: 000000e17aadee18] 
000000e17aadef00 00007ffb516702bc Hoge.Program.TestSub() [c:\Users\sokoide\Projects\Spike\Hoge\Program.cs @ 45]
000000e17aadef40 00007ffb51670243 Hoge.Program.Test(Hoge.Foo, Hoge.Bar) [c:\Users\sokoide\Projects\Spike\Hoge\Program.cs @ 40]
000000e17aadefb0 00007ffb516700fe Hoge.Program.Main(System.String[]) [c:\Users\sokoide\Projects\Spike\Hoge\Program.cs @ 29]
000000e17aadf2d0 00007ffbb0caa7f3 [GCFrame: 000000e17aadf2d0] 


0:000> !clrstack -a
OS Thread Id: 0x1bd4 (0)
        Child SP               IP Call Site
000000e17aadee18 00007ffbc0718b9c [HelperMethodFrame: 000000e17aadee18] 
000000e17aadef00 00007ffb516702bc Hoge.Program.TestSub() [c:\Users\sokoide\Projects\Spike\Hoge\Program.cs @ 45]
    PARAMETERS:
        this = <no data>

000000e17aadef40 00007ffb51670243 Hoge.Program.Test(Hoge.Foo, Hoge.Bar) [c:\Users\sokoide\Projects\Spike\Hoge\Program.cs @ 40]
    PARAMETERS:
        this = <no data>
        foo = <no data>
        bar = <no data>
    LOCALS:
        <no data>

000000e17aadefb0 00007ffb516700fe Hoge.Program.Main(System.String[]) [c:\Users\sokoide\Projects\Spike\Hoge\Program.cs @ 29]
    PARAMETERS:
        args = <no data>

If you see the JIT compiled code for Hoge.Program.Test(Hoge.Foo, Hoge.Bar), you can confirm that rcx is copied in rbi, and the rbi is pushed into the stack at 00007ffb`51670123.

0:000> !U 00007ffb51670243 
Normal JIT generated code
Hoge.Program.Test(Hoge.Foo, Hoge.Bar)
Begin 00007ffb51670120, size 131

c:\Users\sokoide\Projects\Spike\Hoge\Program.cs @ 34:
00007ffb`51670120 53              push    rbx
00007ffb`51670121 55              push    rbp
00007ffb`51670122 56              push    rsi
00007ffb`51670123 57              push    rdi
00007ffb`51670124 4154            push    r12
00007ffb`51670126 4155            push    r13
00007ffb`51670128 4883ec38        sub     rsp,38h
00007ffb`5167012c 498be8          mov     rbp,r8
00007ffb`5167012f 488bfa          mov     rdi,rdx
00007ffb`51670132 488bf1          mov     rsi,rcx
00007ffb`51670135 33db            xor     ebx,ebx
00007ffb`51670137 bab0000000      mov     edx,0B0h
00007ffb`5167013c b901000000      mov     ecx,1

If you can get stack base address when Test(Hoge.Foo, Hoge.Bar) is called, you can get those copied registers which have the Hoge.Foo/Bar arguments.
Since child-sp when the Test function is called is 000000E17AADEFB0, 000000E17AADEFB0-0x8 is the ret address, and 000000E17AADEFB0-0x10 is the base address as below. Then you can get rcx (this), rdx (1st arg), r8 (2nd arg) stored in rsi(<-rcx), rdi(<-rdx), rbp(<-r8) respectively.

0:000> dq 000000e1`7aadef40 000000e1`7aadefff
[7aadef40 – 7aadef78] = 0x38 … local vars
000000e1`7aadef40  000000e1`00002d38 00000000`00000000
000000e1`7aadef50  000000e1`00005b0c 00000000`00000000
000000e1`7aadef60  000000e1`00000009 000000e1`00002d38
000000e1`7aadef70  000000e1`00002d68 000000e1`7aadf300 (r13)
000000e1`7aadef80  000000e1`7aadf348(r12) 000000e1`00002d50 (rdi)
000000e1`7aadef90  000000e1`00002d38(rsi) 000000e1`7aadf010 (rbp)
000000e1`7aadefa0  000000e1`00002d68(rbx) 00007ffb`516700fe (ret)
000000e1`7aadefb0  000000e1`00002d38(child-sp) 000000e1`00002d50
000000e1`7aadefc0  000000e1`00002d68 000000e1`00000001
000000e1`7aadefd0  000000e1`7aadf2d0 000000e1`7aadf128
000000e1`7aadefe0  000000e1`7aadf1a0 00007ffb`b0caa7f3
000000e1`7aadeff0  000000e1`00002cc8 000000e1`7aadf500

this = rcx)
0:000> !do 000000e1`00002d38
Name:        Hoge.Program
MethodTable: 00007ffb51554038
EEClass:     00007ffb516624a8
Size:        24(0x18) bytes
File:        C:\Users\sokoide\Projects\Spike\Hoge\bin\Release\Hoge.exe
Fields:
None

1st arg = rdx)
0:000> !do 000000e1`00002d50
Name:        Hoge.Foo
MethodTable: 00007ffb51554120
EEClass:     00007ffb51662520
Size:        24(0x18) bytes
File:        C:\Users\sokoide\Projects\Spike\Hoge\bin\Release\Hoge.exe
Fields:
              MT    Field   Offset                 Type VT     Attr            Value Name
00007ffbafad0e08  4000012        8        System.String  0 instance 000000e100002ce8 <Name>k__BackingField

2nd arg = r8)
0:000> !do 000000e1`7aadf010
<Note: this object has an invalid CLASS field>
Invalid object

… looks the pointer doesn’t have the class info. However, we know the type is Hoge.Bar!
0:000> !dumpheap -type Bar
         Address               MT     Size
000000e100002d68 00007ffb51554208       24     

Statistics:
              MT    Count    TotalSize Class Name
00007ffb51554208        1           24 Hoge.Bar
Total 1 objects

then we can get the object detail using !dumpvc and the method table and the object pointer as below.
0:000> !dumpvc 00007ffb51554208 000000e1`7aadf010
Name:        Hoge.Bar
MethodTable: 00007ffb51554208
EEClass:     00007ffb51662598
Size:        24(0x18) bytes
File:        C:\Users\sokoide\Projects\Spike\Hoge\bin\Release\Hoge.exe
Fields:
              MT    Field   Offset                 Type VT     Attr            Value Name
00007ffbafad0e08  4000013        0        System.String  0 instance 000000e17aadf150 <Name>k__BackingField

Here is the target application which was used to demonstrate it.

class Foo
{
    public string Name { get; set; }
}

class Bar
{
    public string Name { get; set; }
}

class Program
{
    static void Main(string[] args)
    {
        Program p = new Program();

        Foo foo = new Foo { Name = "hoge" };
        Bar bar = new Bar { Name = "page" };
        p.Test(foo, bar);
    }

    private void Test(Foo foo, Bar bar)
    {
        for (int i = 0; i < 10; i++)
        {
            Console.WriteLine("Hello {0}", i);
        }
        Console.WriteLine(foo.Name);
        Console.WriteLine(bar.Name);
        TestSub();
    }

    private void TestSub()
    {
        throw new Exception("dummy exception");
    }
}

DEBUGGING GO PROGRAM WITH GDB #2

I fixed find_goroutine(goid) using yesterday’s Allg class and finally I’m able to debug goroutines.Thank you Don!
I uploaded entire runtime-gdb.py to my github repo at https://github.com/sokoide/go-gdb.

(gdb) info goroutine
  1 waiting  fname=runtime.gopark faddr=0x13415 &g=0xc208000120 waitreason="sleep"
  2 waiting  fname=runtime.gopark faddr=0x13415 &g=0xc208000480 waitreason="force gc (idle)"
  3 waiting  fname=runtime.gopark faddr=0x13415 &g=0xc2080005a0 waitreason="GC sweep wait"
  4 waiting  fname=runtime.gopark faddr=0x13415 &g=0xc2080006c0 waitreason="finalizer wait"
  5 syscall  fname=runtime.switchtoM faddr=0x366c0 &g=0xc2080007e0

(gdb) goroutine 1 bt
#0  runtime.gopark (unlockf=0x2eba0 <runtime.parkunlock_c>, lock=0x1576e0 <runtime.timers>, reason="sleep")
    at /Users/sokoide/repo/go/src/runtime/proc.go:131
#1  0x0000000000013488 in runtime.goparkunlock (lock=0x1576e0 <runtime.timers>, reason="sleep") at /Users/sokoide/repo/go/src/runtime/proc.go:136
#2  0x0000000000017de5 in runtime.timeSleep (ns=2000000000) at /Users/sokoide/repo/go/src/runtime/time.go:58
#3  0x00000000000020f8 in main.f2 (a=40, b=2, ~r2=833358237544) at /Users/sokoide/workspace/go/foo/foo.go:24
#4  0x0000000000002551 in main.main () at /Users/sokoide/workspace/go/foo/main.go:22

(gdb) goroutine 4 bt
#0  runtime.gopark (unlockf=0x2eba0 <runtime.parkunlock_c>, lock=0x15e840 <runtime.finlock>, reason="finalizer wait")
    at /Users/sokoide/repo/go/src/runtime/proc.go:131
#1  0x0000000000013488 in runtime.goparkunlock (lock=0x15e840 <runtime.finlock>, reason="finalizer wait")
    at /Users/sokoide/repo/go/src/runtime/proc.go:136
#2  0x000000000000e66a in runtime.runfinq () at /Users/sokoide/repo/go/src/runtime/malloc.go:727
#3  0x00000000000388f1 in runtime.goexit () at /Users/sokoide/repo/go/src/runtime/asm_amd64.s:2232
#4  0x0000000000000000 in ?? ()

Here is the updated find_goroutine(good).

def find_goroutine(goid):
	"""
	find_goroutine attempts to find the goroutine identified by goid.
	It returns a touple of gdv.Value's representing the stack pointer
	and program counter pointer for the goroutine.

	@param int goid

	@return tuple (gdb.Value, gdb.Value)
	"""
	__allg = Allg()
	while True:
		ptr = __allg.fetch()
		if not ptr:
			break
		if goid == __allg.Goid(ptr):
		    pc = __allg.Pc(ptr)
		    sp = __allg.Sp(ptr)
		    return pc, sp
	return None, None

Debugging GO program with GDB #1

Golang provides a python script to extend gdb to show goroutines and the callstacks here.

However, it doesn’t work as written.
This patch fixed the first problem, but it still fails (at least on OS X and Windows).

I found this article which I think worked in older GO versions, but it fails in go 1.4.2 (latest as of 2015.3).

I changed Allg class’s offset and init function to show a list of goroutines as below.

(gdb) info goroutines
  1 waiting  fname=runtime.gopark faddr=0x13585 &g=0xc208000120 waitreason="sleep"
  2 waiting  fname=runtime.gopark faddr=0x13585 &g=0xc208000480 waitreason="force gc (idle)"
  3 waiting  fname=runtime.gopark faddr=0x13585 &g=0xc2080005a0 waitreason="GC sweep wait"
  4 waiting  fname=runtime.gopark faddr=0x13585 &g=0xc2080006c0 waitreason="finalizer wait"
  5 syscall  fname=runtime.switchtoM faddr=0x36830 &g=0xc208000a20

I’ll need more work to fix ‘go $goroutineid bt’.

(gdb) goroutine 1 bt
Python Exception <class 'gdb.error'> There is no member named status.:
Error occurred in Python command: There is no member named status.

Today’s change.

runtime/runtime-gdb.py:
class Allg:
    __allglen = -1
    __position = 0
    __allg = 0

    __offsets = {
            'status': 120,
            'waitreason': 144,
            'goid': 128,
            'm': 200,
            'sched': 48,
            'sched.pc': 56,
            'sched.sp': 48,
            'stackguard': 16,
            'stackbase': 0,
        }

    def __init__(self):
        # first, fetch the number of active goroutines
        self.__allglen = int(str(gdb.parse_and_eval("&{uint64}'runtime.allglen'")), 16)
        # print("found allglen = {0}".format(self.__allglen))

        # get the next address in the array
        s = "&*{uint64}(&'runtime.allg')"
        self.__allg = int(gdb.parse_and_eval(s))
        # print("found allg = {0}".format(hex(self.__allg)))

    def fetch(self):
        if self.__position >= self.__allglen:
            return None

        s = "&*{uint64}(" + "{0}+{1})".format(self.__allg, self.__position*8)
        p = int(gdb.parse_and_eval(s))
        self.__position += 1
        return p

    def Status(self, a):
        s = "&*{int16}(" + "{0}+{1})".format(a, self.__offsets['status'])
        return int(gdb.parse_and_eval(s))

    def WaitReason(self, a):
        s = "&*{int64}(" + "{0}+{1})".format(a, self.__offsets['waitreason'])
        x = int(gdb.parse_and_eval(s))
        s = "&{int8}" + "{0}".format(x)
        return str(gdb.parse_and_eval(s))

    def Goid(self, a):
        s = "&*{int64}(" + "{0}+{1})".format(a, self.__offsets['goid'])
        return int(gdb.parse_and_eval(s))

    def M(self, a):
        s = "&*{uint64}(" + "{0}+{1})".format(a, self.__offsets['m'])
        return int(gdb.parse_and_eval(s))

    def Pc(self, a):
        s = "&*{uint64}(" + "{0}+{1})".format(a, self.__offsets['sched.pc'])
        return int(gdb.parse_and_eval(s))

    def Sp(self, a):
        s = "&*{uint64}(" + "{0}+{1})".format(a, self.__offsets['sched.sp'])
        return int(gdb.parse_and_eval(s))

    def Stackguard(self, a):
        s = "&*{uint64}(" + "{0}+{1})".format(a, self.__offsets['stackguard'])
        return int(gdb.parse_and_eval(s))

    def Stackbase(self, a):
        s = "&*{uint64}(" + "{0}+{1})".format(a, self.__offsets['stackbase'])
        return int(gdb.parse_and_eval(s))


class GoroutinesCmd(gdb.Command):
	"List all goroutines."
	__allg = None

	def __init__(self):
		gdb.Command.__init__(self, "info goroutines", gdb.COMMAND_STACK, gdb.COMPLETE_NONE)

	def invoke(self, _arg, _from_tty):
		self.__allg = Allg()
		while True:
			ptr = self.__allg.fetch()
			# print("fetched ptr = {0}".format(hex(ptr)))
			if not ptr:
				break

			st = self.__allg.Status(ptr)
			# print("status is {0}".format(st))
			w = self.__allg.WaitReason(ptr)
			# print("waitreason is {0}".format(w))
			#if st == 6:  # 'gdead'
			    #print("skipping over dead goroutine")
			    #continue

			s = ' '
			m = self.__allg.M(ptr)
			if m:
				s = '*'

			# if the status isn't "waiting" then the waitreason doesn' tmatter
			if st != 4:
				w = ''
			w2 = w.split('"')
			if len(w2) > 1:
				w = """waitreason="{0}\"""".format(w2[len(w2) - 2])

			pc = self.__allg.Pc(ptr)
			# print("pc is {0}".format(pc))
                        blk = gdb.block_for_pc(pc)
                        # print("blk is {0}".format(blk))
                        goid = self.__allg.Goid(ptr)
                        a = "fname={0} faddr={1}".format(blk.function, hex(pc))
                        print(s, goid, "{0:8s}".format(sts[st]), a, "&g={0}".format(hex(ptr)), w)

The __offsets in Allg was calculated from go’s runtime zruntime_defs_darwin_amd64.go.

 123 type g struct {
 124 ▸ stack        stack          // 0, +16
 125 ▸ stackguard0  uintptr        // 16
 126 ▸ stackguard1  uintptr        // 24
 127 ▸ _panic       *_panic        // 32
 128 ▸ _defer       *_defer        // 40
 129 ▸ sched        gobuf          // 48, +48
 130 ▸ syscallsp    uintptr        // 96
 131 ▸ syscallpc    uintptr        // 104
 132 ▸ param        unsafe.Pointer // 112
 133 ▸ atomicstatus uint32         // 120, padding +4
 134 ▸ goid         int64          // 128
 135 ▸ waitsince    int64          // 136
 136 ▸ waitreason   string         // 144, +16
 137 ▸ schedlink    *g             // 160
 138 ▸ issystem     bool           // 168
 139 ▸ preempt      bool           // 172
 140 ▸ paniconfault bool           //176
 141 ▸ preemptscan  bool           //180
 142 ▸ gcworkdone   bool           //184
 143 ▸ throwsplit   bool           //188
 144 ▸ raceignore   int8           //192, padding +7
 145 ▸ m            *m             //200

x86_64 calling convention

As written in Wikipedia, Linux/MacOS X uses RDI, RSI, RDX, RCX, R8, and R9 for the first 6 args (uses XMM0-7 fro float) + stack for the others.

Windows uses RCX, RDX, R8, R9 (uses XMM0-3 for float) + stack.

#include <iostream>

using namespace std;

int foo(int a, int b, int c, int d, int e, int f, int g){
  int r = a+b+c+d+e+f+g;
  return r;
}

int main(int argc, char const* argv[])
{
  cout << foo(1,2,3,4,5,6,7);
  return 0;
}

MacOS X 10.9:

(lldb) disassemble --name main
foo`main:
foo[0x100000ec0]:  push   rbp
foo[0x100000ec1]:  mov    rbp, rsp
foo[0x100000ec4]:  push   rbx
foo[0x100000ec5]:  sub    rsp, 0x38
foo[0x100000ec9]:  mov    eax, 0x1
foo[0x100000ece]:  mov    ecx, 0x2
foo[0x100000ed3]:  mov    edx, 0x3
foo[0x100000ed8]:  mov    r8d, 0x4
foo[0x100000ede]:  mov    r9d, 0x5
foo[0x100000ee4]:  mov    r10d, 0x6
foo[0x100000eea]:  mov    r11d, 0x7
foo[0x100000ef0]:  mov    rbx, qword ptr [rip + 0x121] ; (void *)0x0000000000000000
foo[0x100000ef7]:  mov    rbx, qword ptr [rbx]
foo[0x100000efa]:  mov    qword ptr [rbp - 0x10], rbx
foo[0x100000efe]:  mov    dword ptr [rbp - 0x14], 0x0
foo[0x100000f05]:  mov    dword ptr [rbp - 0x18], edi
foo[0x100000f08]:  mov    qword ptr [rbp - 0x20], rsi
foo[0x100000f0c]:  mov    edi, eax
foo[0x100000f0e]:  mov    esi, ecx
foo[0x100000f10]:  mov    ecx, r8d
foo[0x100000f13]:  mov    r8d, r9d
foo[0x100000f16]:  mov    r9d, r10d
foo[0x100000f19]:  mov    dword ptr [rsp], 0x7
foo[0x100000f20]:  mov    dword ptr [rbp - 0x24], r11d
foo[0x100000f24]:  call   0x100000e50               ; foo(int, int, int, int, int, int, int)
foo[0x100000f29]:  mov    rdi, qword ptr [rip + 0xe0] ; (void *)0x0000000000000000
foo[0x100000f30]:  mov    esi, eax
foo[0x100000f32]:  call   0x100000f60               ; symbol stub for: std::__1::basic_ostream<char, std::__1::char_traits<char> >::operator<<(int)
foo[0x100000f37]:  mov    rdi, qword ptr [rip + 0xda] ; (void *)0x0000000000000000
foo[0x100000f3e]:  mov    rdi, qword ptr [rdi]
foo[0x100000f41]:  cmp    rdi, qword ptr [rbp - 0x10]
foo[0x100000f45]:  mov    qword ptr [rbp - 0x30], rax
foo[0x100000f49]:  jne    0x100000f5b               ; main + 155
foo[0x100000f4f]:  mov    eax, 0x0
foo[0x100000f54]:  add    rsp, 0x38
foo[0x100000f58]:  pop    rbx
foo[0x100000f59]:  pop    rbp
foo[0x100000f5a]:  ret    
foo[0x100000f5b]:  call   0x100000f66               ; symbol stub for: __stack_chk_fail
 
(lldb) disassemble --name foo
foo`foo(int, int, int, int, int, int, int):
foo[0x100000e50]:  push   rbp
foo[0x100000e51]:  mov    rbp, rsp
foo[0x100000e54]:  sub    rsp, 0x30
foo[0x100000e58]:  mov    eax, dword ptr [rbp + 0x10]
foo[0x100000e5b]:  mov    r10, qword ptr [rip + 0x1b6] ; (void *)0x0000000000000000
foo[0x100000e62]:  mov    r11, qword ptr [r10]
foo[0x100000e65]:  mov    qword ptr [rbp - 0x8], r11
foo[0x100000e69]:  mov    dword ptr [rbp - 0xc], edi
foo[0x100000e6c]:  mov    dword ptr [rbp - 0x10], esi
foo[0x100000e6f]:  mov    dword ptr [rbp - 0x14], edx
foo[0x100000e72]:  mov    dword ptr [rbp - 0x18], ecx
foo[0x100000e75]:  mov    dword ptr [rbp - 0x1c], r8d
foo[0x100000e79]:  mov    dword ptr [rbp - 0x20], r9d
foo[0x100000e7d]:  mov    dword ptr [rbp - 0x24], eax
foo[0x100000e80]:  mov    eax, dword ptr [rbp - 0xc]
foo[0x100000e83]:  add    eax, dword ptr [rbp - 0x10]
foo[0x100000e86]:  add    eax, dword ptr [rbp - 0x14]
foo[0x100000e89]:  add    eax, dword ptr [rbp - 0x18]
foo[0x100000e8c]:  add    eax, dword ptr [rbp - 0x1c]
foo[0x100000e8f]:  add    eax, dword ptr [rbp - 0x20]
foo[0x100000e92]:  add    eax, dword ptr [rbp - 0x24]
foo[0x100000e95]:  mov    dword ptr [rbp - 0x28], eax
foo[0x100000e98]:  mov    eax, dword ptr [rbp - 0x28]
foo[0x100000e9b]:  mov    r10, qword ptr [r10]
foo[0x100000e9e]:  cmp    r10, qword ptr [rbp - 0x8]
foo[0x100000ea2]:  mov    dword ptr [rbp - 0x2c], eax
foo[0x100000ea5]:  jne    0x100000eb4               ; foo(int, int, int, int, int, int, int) + 100
foo[0x100000eab]:  mov    eax, dword ptr [rbp - 0x2c]
foo[0x100000eae]:  add    rsp, 0x30
foo[0x100000eb2]:  pop    rbp
foo[0x100000eb3]:  ret    
foo[0x100000eb4]:  call   0x100000f66               ; symbol stub for: __stack_chk_fail
foo[0x100000eb9]:  nop    dword ptr [rax]

Windows 8:

0:000> uf foo!main (int, char **)
foo!main [c:\users\sokoide\projects\spike\foo\foo.cpp @ 16]:
   16 00007ff7`fbfd2400 4889542410      mov     qword ptr [rsp+10h],rdx
   16 00007ff7`fbfd2405 894c2408        mov     dword ptr [rsp+8],ecx
   16 00007ff7`fbfd2409 57              push    rdi
   16 00007ff7`fbfd240a 4883ec40        sub     rsp,40h
   16 00007ff7`fbfd240e 488bfc          mov     rdi,rsp
   16 00007ff7`fbfd2411 b910000000      mov     ecx,10h
   16 00007ff7`fbfd2416 b8cccccccc      mov     eax,0CCCCCCCCh
   16 00007ff7`fbfd241b f3ab            rep stos dword ptr [rdi]
   16 00007ff7`fbfd241d 8b4c2450        mov     ecx,dword ptr [rsp+50h]
   17 00007ff7`fbfd2421 c744243007000000 mov     dword ptr [rsp+30h],7
   17 00007ff7`fbfd2429 c744242806000000 mov     dword ptr [rsp+28h],6
   17 00007ff7`fbfd2431 c744242005000000 mov     dword ptr [rsp+20h],5
   17 00007ff7`fbfd2439 41b904000000    mov     r9d,4
   17 00007ff7`fbfd243f 41b803000000    mov     r8d,3
   17 00007ff7`fbfd2445 ba02000000      mov     edx,2
   17 00007ff7`fbfd244a b901000000      mov     ecx,1
   17 00007ff7`fbfd244f e8f6ecffff      call    foo!ILT+325(?fooYAHHHHHHHHZ) (00007ff7`fbfd114a)
   17 00007ff7`fbfd2454 8bd0            mov     edx,eax
   17 00007ff7`fbfd2456 488b0dc3ec0000  mov     rcx,qword ptr [foo!_imp_?coutstd (00007ff7`fbfe1120)]
   17 00007ff7`fbfd245d ff15c5ec0000    call    qword ptr [foo!_imp_??6?$basic_ostreamDU?$char_traitsDstdstdQEAAAEAV01HZ (00007ff7`fbfe1128)]
   18 00007ff7`fbfd2463 33c0            xor     eax,eax
   19 00007ff7`fbfd2465 4883c440        add     rsp,40h
   19 00007ff7`fbfd2469 5f              pop     rdi
   19 00007ff7`fbfd246a c3              ret
                   ^ Extra character error in 'uf foo!main (int, char **)'
0:000> uf foo!foo (int, int, int, int, int, int, int)
foo!foo [c:\users\sokoide\projects\spike\foo\foo.cpp @ 10]:
   10 00007ff7`fbfd23a0 44894c2420      mov     dword ptr [rsp+20h],r9d
   10 00007ff7`fbfd23a5 4489442418      mov     dword ptr [rsp+18h],r8d
   10 00007ff7`fbfd23aa 89542410        mov     dword ptr [rsp+10h],edx
   10 00007ff7`fbfd23ae 894c2408        mov     dword ptr [rsp+8],ecx
   10 00007ff7`fbfd23b2 57              push    rdi
   10 00007ff7`fbfd23b3 4883ec10        sub     rsp,10h
   10 00007ff7`fbfd23b7 488bfc          mov     rdi,rsp
   10 00007ff7`fbfd23ba b904000000      mov     ecx,4
   10 00007ff7`fbfd23bf b8cccccccc      mov     eax,0CCCCCCCCh
   10 00007ff7`fbfd23c4 f3ab            rep stos dword ptr [rdi]
   10 00007ff7`fbfd23c6 8b4c2420        mov     ecx,dword ptr [rsp+20h]
   11 00007ff7`fbfd23ca 8b442428        mov     eax,dword ptr [rsp+28h]
   11 00007ff7`fbfd23ce 8b4c2420        mov     ecx,dword ptr [rsp+20h]
   11 00007ff7`fbfd23d2 03c8            add     ecx,eax
   11 00007ff7`fbfd23d4 8bc1            mov     eax,ecx
   11 00007ff7`fbfd23d6 03442430        add     eax,dword ptr [rsp+30h]
   11 00007ff7`fbfd23da 03442438        add     eax,dword ptr [rsp+38h]
   11 00007ff7`fbfd23de 03442440        add     eax,dword ptr [rsp+40h]
   11 00007ff7`fbfd23e2 03442448        add     eax,dword ptr [rsp+48h]
   11 00007ff7`fbfd23e6 03442450        add     eax,dword ptr [rsp+50h]
   11 00007ff7`fbfd23ea 890424          mov     dword ptr [rsp],eax
   12 00007ff7`fbfd23ed 8b0424          mov     eax,dword ptr [rsp]
   13 00007ff7`fbfd23f0 4883c410        add     rsp,10h
   13 00007ff7`fbfd23f4 5f              pop     rdi
   13 00007ff7`fbfd23f5 c3              ret
                  ^ Extra character error in 'uf foo!foo (int, int, int, int, int, int, int)'

My first gdb

Wanted to use gdb since I’m always using Windows debuggers at work. Good thing is that now MacOS runs on Intel CPU and I don’t need to look into X68000 or PowerPC.

Here is today’s test target.

 #include <stdio.h>
 
 bool hogecheck();
 void page();
 
 bool hogecheck(){
     return false;
 }         
 
 void page(){
     printf("page");
 }
 
 int main(int args, char* argv[])
 {   
     int i;
     if(false == hogecheck()){
         return 1;
     }
     page();
     return 0;
 }

And here is how I changed the result of ‘hogecheck’ and run ‘page’ function.

(gdb) b main
Breakpoint 1 at 0x100000e7f: file first.c, line 17.
(gdb) g
Undefined command: "g".  Try "help".
(gdb) r
...
(gdb) set disassembly-flavor intel
(gdb) disas
Dump of assembler code for function main:
0x0000000100000e70 <main+0>:	push   rbp
0x0000000100000e71 <main+1>:	mov    rbp,rsp
0x0000000100000e74 <main+4>:	sub    rsp,0x20
0x0000000100000e78 <main+8>:	mov    DWORD PTR [rbp-0x4],edi
0x0000000100000e7b <main+11>:	mov    QWORD PTR [rbp-0x10],rsi
0x0000000100000e7f <main+15>:	call   0x100000e30 <_Z9hogecheckv>
0x0000000100000e84 <main+20>:	mov    cl,al
0x0000000100000e86 <main+22>:	cmp    cl,0x0
0x0000000100000e89 <main+25>:	setne  cl
0x0000000100000e8c <main+28>:	xor    cl,0x1
0x0000000100000e8f <main+31>:	and    cl,0x1
0x0000000100000e92 <main+34>:	mov    BYTE PTR [rbp-0x19],cl
0x0000000100000e95 <main+37>:	mov    cl,BYTE PTR [rbp-0x19]
0x0000000100000e98 <main+40>:	cmp    cl,0x0
0x0000000100000e9b <main+43>:	je     0x100000ea6 <main+54>
0x0000000100000e9d <main+45>:	mov    DWORD PTR [rbp-0x18],0x1
0x0000000100000ea4 <main+52>:	jmp    0x100000eb2 <main+66>
0x0000000100000ea6 <main+54>:	call   0x100000e50 <_Z4pagev>
0x0000000100000eab <main+59>:	mov    DWORD PTR [rbp-0x18],0x0
0x0000000100000eb2 <main+66>:	mov    eax,DWORD PTR [rbp-0x18]
0x0000000100000eb5 <main+69>:	mov    DWORD PTR [rbp-0x14],eax
0x0000000100000eb8 <main+72>:	mov    eax,DWORD PTR [rbp-0x14]
0x0000000100000ebb <main+75>:	add    rsp,0x20
0x0000000100000ebf <main+79>:	pop    rbp
0x0000000100000ec0 <main+80>:	ret    

(gdb) info r
rax            0x100000e70	4294970992
rbx            0x0	0
...
rip            0x100000e7f	0x100000e7f <main+15> <-- Stopped one step before

(gdb) n

Breakpoint 2, 0x0000000100000e84 in main (args=1, argv=0x7fff5fbff9e0) at first.c:17
17		if(false == hogecheck()){

(gdb) info r
rax            0x0	0
rbx            0x0	0
...
rip            0x100000e84	0x100000e84 <main+20>

(gdb) set $rax=1
(gdb) i r 
rax            0x1	1 <-- set
rbx            0x0	0

(gdb) c
Continuing.
page <-- displayed
Program exited normally.

Windbg version is here.

0:000> X first!*main*
00000000`00d03014 first!__native_dllmain_reason = 0xffffffff
00000000`00d03018 first!mainret = 0n0
00000000`00d01000 first!wmain (int, wchar_t **)
00000000`00d01119 first!__tmainCRTStartup (void)
00000000`00d0127e first!wmainCRTStartup (void)
00000000`00d02084 first!_imp____wgetmainargs = <no type information>
0:000> bp first!wmain
0:000> bl
 0 e x86 00000000`01131470     0001 (0001)  0:**** first!wmain
0:000:x86> g
Breakpoint 0 hit
first!wmain:
01131470 55              push    ebp
0:000:x86> u eip eip+100
first!wmain [c:\users\sokoide\projects\spike\first\first.cpp @ 19]:
01131470 55              push    ebp
01131471 8bec            mov     ebp,esp
01131473 81eccc000000    sub     esp,0CCh
01131479 53              push    ebx
0113147a 56              push    esi
0113147b 57              push    edi
0113147c 8dbd34ffffff    lea     edi,[ebp-0CCh]
01131482 b933000000      mov     ecx,33h
01131487 b8cccccccc      mov     eax,0CCCCCCCCh
0113148c f3ab            rep stos dword ptr es:[edi]
0113148e e881fbffff      call    first!ILT+15(?hogecheckYA_NXZ) (01131014)
01131493 0fb6c0          movzx   eax,al
01131496 85c0            test    eax,eax
01131498 7507            jne     first!wmain+0x31 (011314a1)
0113149a b801000000      mov     eax,1
0113149f eb07            jmp     first!wmain+0x38 (011314a8)
011314a1 e8e1fbffff      call    first!ILT+130(?pageYAXXZ) (01131087)
011314a6 33c0            xor     eax,eax
011314a8 5f              pop     edi
011314a9 5e              pop     esi
011314aa 5b              pop     ebx
011314ab 81c4cc000000    add     esp,0CCh
011314b1 3bec            cmp     ebp,esp
011314b3 e897fcffff      call    first!ILT+330(__RTC_CheckEsp) (0113114f)
011314b8 8be5            mov     esp,ebp
011314ba 5d              pop     ebp
011314bb c3              ret
011314bc cc              int     3
0:000:x86> bp 01131496
0:000:x86> g
Breakpoint 1 hit
first!wmain+0x26:
01131496 85c0            test    eax,eax
0:000:x86> r @eax=1
0:000:x86> r
eax=00000001 ebx=7f5fe000 ecx=00000000 edx=00000001 esi=00000000 edi=00b1fa4c
eip=01131496 esp=00b1f974 ebp=00b1fa4c iopl=0         nv up ei pl zr na pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000246
first!wmain+0x26:
01131496 85c0            test    eax,eax
0:000:x86> g

-> 'page' displayed in the console.

Automatic crash collection

I found an awesome service at https://www.crittercism.com.
You can download and set it up within 30 mins with only 3 line additions / lib additions. You’ll get crash reports with callstack. If you send a symbol file (dSYM), you’ll get a name resolved stack.

Add these lines

#import "Crittercism.h"
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    // crash report
    [Crittercism initWithAppID: kAppId];
    ...

To test, add this line

    [NSException raise:NSInvalidArgumentException
                format:@"Hoge must not be nil"];

To get UUID of the binary,

dwarfdump --uuid /Volumes/RamDisk/Xcode/DerivedData/.../Products/Debug-iphoneos/iEijiroIap.app/iEijiroIap 
UUID: 02BAD808-5764-3AD9-B977-7101C7DEC749 (armv7) /Volumes/RamDisk/Xcode/...

Import Lookup Table

Came back to Windows world and played with windbg.

The following code showed f1096 as a function pointer of ‘add’, but the final address was f13c0. Visual Studio compiler creates function address table ILT which makes relative jump to the actual functions.

CppGeneral.cpp:

#include "stdafx.h"

int add(int a, int b);

typedef int (*PTR_ADD)(int, int);

int add(int a, int b){
    return a+b;
}

int _tmain(int argc, _TCHAR* argv[])
{
    PTR_ADD pAdd = add;

    _tprintf(_T("%d\n"), add(3, 4));
    _tprintf(_T("%d, address=%x\n"), pAdd(3, 4), pAdd);

    getchar();
    return 0;
}

Output:

7
7, address = f1096

Debug log:

0:001> uf CppGeneral!wmain
CppGeneral!wmain [c:\users\sokoide\projects\general\cppgeneral\cppgeneral.cpp @ 15]:
   15 000f1a70 55              push    ebp
   15 000f1a71 8bec            mov     ebp,esp
   15 000f1a73 81eccc000000    sub     esp,0CCh
   15 000f1a79 53              push    ebx
   15 000f1a7a 56              push    esi
   15 000f1a7b 57              push    edi
   15 000f1a7c 8dbd34ffffff    lea     edi,[ebp-0CCh]
   15 000f1a82 b933000000      mov     ecx,33h
   15 000f1a87 b8cccccccc      mov     eax,0CCCCCCCCh
   15 000f1a8c f3ab            rep stos dword ptr es:[edi]
   16 000f1a8e c745f896100f00  mov     dword ptr [ebp-8],offset CppGeneral!ILT+145(?addYAHHHZ) (000f1096)
   18 000f1a95 6a04            push    4
   18 000f1a97 6a03            push    3
   18 000f1a99 e8f8f5ffff      call    CppGeneral!ILT+145(?addYAHHHZ) (000f1096)
   18 000f1a9e 83c408          add     esp,8
   18 000f1aa1 8bf4            mov     esi,esp
   18 000f1aa3 50              push    eax
   18 000f1aa4 683c570f00      push    offset CppGeneral!`string' (000f573c)
   18 000f1aa9 ff15d4820f00    call    dword ptr [CppGeneral!_imp__wprintf (000f82d4)]
   18 000f1aaf 83c408          add     esp,8
   18 000f1ab2 3bf4            cmp     esi,esp
   18 000f1ab4 e887f6ffff      call    CppGeneral!ILT+315(__RTC_CheckEsp) (000f1140)
   19 000f1ab9 8bf4            mov     esi,esp
   19 000f1abb 8b45f8          mov     eax,dword ptr [ebp-8]
   19 000f1abe 50              push    eax
   19 000f1abf 8bfc            mov     edi,esp
   19 000f1ac1 6a04            push    4
   19 000f1ac3 6a03            push    3
   19 000f1ac5 ff55f8          call    dword ptr [ebp-8]
   19 000f1ac8 83c408          add     esp,8
   19 000f1acb 3bfc            cmp     edi,esp
   19 000f1acd e86ef6ffff      call    CppGeneral!ILT+315(__RTC_CheckEsp) (000f1140)
   19 000f1ad2 50              push    eax
   19 000f1ad3 68e05a0f00      push    offset CppGeneral!`string' (000f5ae0)
   19 000f1ad8 ff15d4820f00    call    dword ptr [CppGeneral!_imp__wprintf (000f82d4)]
   19 000f1ade 83c40c          add     esp,0Ch
   19 000f1ae1 3bf4            cmp     esi,esp
   19 000f1ae3 e858f6ffff      call    CppGeneral!ILT+315(__RTC_CheckEsp) (000f1140)
   21 000f1ae8 8bf4            mov     esi,esp
   21 000f1aea ff15d8820f00    call    dword ptr [CppGeneral!_imp__getchar (000f82d8)]
   21 000f1af0 3bf4            cmp     esi,esp
   21 000f1af2 e849f6ffff      call    CppGeneral!ILT+315(__RTC_CheckEsp) (000f1140)
   22 000f1af7 33c0            xor     eax,eax
   23 000f1af9 5f              pop     edi
   23 000f1afa 5e              pop     esi
   23 000f1afb 5b              pop     ebx
   23 000f1afc 81c4cc000000    add     esp,0CCh
   23 000f1b02 3bec            cmp     ebp,esp
   23 000f1b04 e837f6ffff      call    CppGeneral!ILT+315(__RTC_CheckEsp) (000f1140)
   23 000f1b09 8be5            mov     esp,ebp
   23 000f1b0b 5d              pop     ebp
   23 000f1b0c c3              ret


0:001> X CppGeneral!a*
000f7144 CppGeneral!argv = 0x003a1480
000f713c CppGeneral!argc = 0n1
000f13c0 CppGeneral!add (int, int)             // <-- add is at f13c0
000f2880 CppGeneral!atexit (<function> *)


0:001> u f1096
CppGeneral!ILT+145(?addYAHHHZ):
000f1096 e925030000      jmp     CppGeneral!add (000f13c0)   // <-- f1096 is calling jmp to f13c0


(It seems ILT is stored here)
0:001> u f1004 f1100
CppGeneral!_enc$textbss$end <PERF> (CppGeneral+0x11004):
000f1004 cc              int     3
CppGeneral!ILT+0(__setdefaultprecision):
000f1005 e936160000      jmp     CppGeneral!_setdefaultprecision (000f2640)
CppGeneral!ILT+5(_wmain):
000f100a e9610a0000      jmp     CppGeneral!wmain (000f1a70)
...
000f1096 e925030000      jmp     CppGeneral!add (000f13c0)
CppGeneral!ILT+150(__exit):
000f109b e938190000      jmp     CppGeneral!exit (000f29d8)
...


(add starts at f13c0)
0:001> uf CppGeneral!add
CppGeneral!add [c:\users\sokoide\projects\general\cppgeneral\cppgeneral.cpp @ 10]:
   10 000f13c0 55              push    ebp
   10 000f13c1 8bec            mov     ebp,esp
   10 000f13c3 81ecc0000000    sub     esp,0C0h
   10 000f13c9 53              push    ebx
   10 000f13ca 56              push    esi
   10 000f13cb 57              push    edi
   10 000f13cc 8dbd40ffffff    lea     edi,[ebp-0C0h]
   10 000f13d2 b930000000      mov     ecx,30h
   10 000f13d7 b8cccccccc      mov     eax,0CCCCCCCCh
   10 000f13dc f3ab            rep stos dword ptr es:[edi]
   11 000f13de 8b4508          mov     eax,dword ptr [ebp+8]
   11 000f13e1 03450c          add     eax,dword ptr [ebp+0Ch]
   12 000f13e4 5f              pop     edi
   12 000f13e5 5e              pop     esi
   12 000f13e6 5b              pop     ebx
   12 000f13e7 8be5            mov     esp,ebp
   12 000f13e9 5d              pop     ebp
   12 000f13ea c3              ret