From 1d4747c35fe5627375c98ab0cb55ec1e4e0eab4e Mon Sep 17 00:00:00 2001 From: Alexandre Julliard Date: Wed, 20 May 2009 12:31:28 +0200 Subject: [PATCH] ntdll: Add support for some function epilogs in RtlVirtualUnwind. --- dlls/ntdll/signal_x86_64.c | 132 ++++++++++++++++++++++++++++++++++- dlls/ntdll/tests/exception.c | 10 +++ 2 files changed, 141 insertions(+), 1 deletion(-) diff --git a/dlls/ntdll/signal_x86_64.c b/dlls/ntdll/signal_x86_64.c index c1210e9db0e..bf78685c3dc 100644 --- a/dlls/ntdll/signal_x86_64.c +++ b/dlls/ntdll/signal_x86_64.c @@ -1060,6 +1060,131 @@ static int get_opcode_size( struct opcode op ) } } +static BOOL is_inside_epilog( BYTE *pc ) +{ + /* add or lea must be the first instruction, and it must have a rex.W prefix */ + if ((pc[0] & 0xf8) == 0x48) + { + switch (pc[1]) + { + case 0x81: /* add $nnnn,%rsp */ + if (pc[0] == 0x48 && pc[2] == 0xc4) + { + pc += 7; + break; + } + return FALSE; + case 0x83: /* add $n,%rsp */ + if (pc[0] == 0x48 && pc[2] == 0xc4) + { + pc += 4; + break; + } + return FALSE; + case 0x8d: /* lea n(reg),%rsp */ + if (pc[0] & 0x06) return FALSE; /* rex.RX must be cleared */ + if (((pc[2] >> 3) & 7) != 4) return FALSE; /* dest reg mus be %rsp */ + if ((pc[2] & 7) == 4) return FALSE; /* no SIB byte allowed */ + if ((pc[2] >> 6) == 1) /* 8-bit offset */ + { + pc += 4; + break; + } + if ((pc[2] >> 6) == 2) /* 32-bit offset */ + { + pc += 7; + break; + } + return FALSE; + } + } + + /* now check for various pop instructions */ + + for (;;) + { + BYTE rex = 0; + + if ((*pc & 0xf0) == 0x40) rex = *pc++ & 0x0f; /* rex prefix */ + + switch (*pc) + { + case 0x58: /* pop %rax/%r8 */ + case 0x59: /* pop %rcx/%r9 */ + case 0x5a: /* pop %rdx/%r10 */ + case 0x5b: /* pop %rbx/%r11 */ + case 0x5c: /* pop %rsp/%r12 */ + case 0x5d: /* pop %rbp/%r13 */ + case 0x5e: /* pop %rsi/%r14 */ + case 0x5f: /* pop %rdi/%r15 */ + pc++; + continue; + case 0xc2: /* ret $nn */ + case 0xc3: /* ret */ + return TRUE; + /* FIXME: add various jump instructions */ + } + return FALSE; + } +} + +/* execute a function epilog, which must have been validated with is_inside_epilog() */ +static void interpret_epilog( BYTE *pc, CONTEXT *context, KNONVOLATILE_CONTEXT_POINTERS *ctx_ptr ) +{ + for (;;) + { + BYTE rex = 0; + + if ((*pc & 0xf0) == 0x40) rex = *pc++ & 0x0f; /* rex prefix */ + + switch (*pc) + { + case 0x58: /* pop %rax/r8 */ + case 0x59: /* pop %rcx/r9 */ + case 0x5a: /* pop %rdx/r10 */ + case 0x5b: /* pop %rbx/r11 */ + case 0x5c: /* pop %rsp/r12 */ + case 0x5d: /* pop %rbp/r13 */ + case 0x5e: /* pop %rsi/r14 */ + case 0x5f: /* pop %rdi/r15 */ + set_int_reg( context, ctx_ptr, *pc - 0x58 + (rex & 1) * 8, *(ULONG64 *)context->Rsp ); + context->Rsp += sizeof(ULONG64); + pc++; + continue; + case 0x81: /* add $nnnn,%rsp */ + context->Rsp += *(LONG *)(pc + 2); + pc += 2 + sizeof(LONG); + continue; + case 0x83: /* add $n,%rsp */ + context->Rsp += (signed char)pc[2]; + pc += 3; + continue; + case 0x8d: + if ((pc[1] >> 6) == 1) /* lea n(reg),%rsp */ + { + context->Rsp = get_int_reg( context, (pc[1] & 7) + (rex & 1) * 8 ) + (signed char)pc[2]; + pc += 3; + } + else /* lea nnnn(reg),%rsp */ + { + context->Rsp = get_int_reg( context, (pc[1] & 7) + (rex & 1) * 8 ) + *(LONG *)(pc + 2); + pc += 2 + sizeof(LONG); + } + continue; + case 0xc2: /* ret $nn */ + context->Rip = *(ULONG64 *)context->Rsp; + context->Rsp += sizeof(ULONG64) + *(WORD *)(pc + 1); + return; + case 0xc3: /* ret */ + context->Rip = *(ULONG64 *)context->Rsp; + context->Rsp += sizeof(ULONG64); + return; + /* FIXME: add various jump instructions */ + } + return; + } +} + /********************************************************************** * RtlVirtualUnwind (NTDLL.@) */ @@ -1100,7 +1225,12 @@ PVOID WINAPI RtlVirtualUnwind( ULONG type, ULONG64 base, ULONG64 pc, else { prolog_offset = ~0; - /* FIXME: check for function epilog */ + if (is_inside_epilog( (BYTE *)pc )) + { + interpret_epilog( (BYTE *)pc, context, ctx_ptr ); + *frame_ret = frame; + return NULL; + } } for (i = 0; i < info->count; i += get_opcode_size(info->opcodes[i])) diff --git a/dlls/ntdll/tests/exception.c b/dlls/ntdll/tests/exception.c index c3cc79748af..ff4125f0f7d 100644 --- a/dlls/ntdll/tests/exception.c +++ b/dlls/ntdll/tests/exception.c @@ -1116,6 +1116,9 @@ static void test_virtual_unwind(void) { 0x1c, 0x40, TRUE, 0x128, { {rsp,0x130}, {rbp,0x120}, {rbx,0x130}, {rsi,0x138}, {-1,-1}}}, { 0x1d, 0x40, TRUE, 0x128, { {rsp,0x130}, {rbp,0x120}, {rbx,0x130}, {rsi,0x138}, {-1,-1}}}, { 0x24, 0x40, TRUE, 0x128, { {rsp,0x130}, {rbp,0x120}, {rbx,0x130}, {rsi,0x138}, {-1,-1}}}, + { 0x2b, 0x40, FALSE, 0x128, { {rsp,0x130}, {rbp,0x120}, {-1,-1}}}, + { 0x32, 0x40, FALSE, 0x008, { {rsp,0x010}, {rbp,0x000}, {-1,-1}}}, + { 0x33, 0x40, FALSE, 0x000, { {rsp,0x008}, {-1,-1}}}, }; @@ -1165,6 +1168,13 @@ static void test_virtual_unwind(void) { 0x04, 0x50, FALSE, 0x020, { {rsp,0x028}, {rbx,0x018}, {rbp,0x010}, {rsi,0x008}, {rdi,0x000}, {-1,-1} }}, { 0x06, 0x50, FALSE, 0x028, { {rsp,0x030}, {rbx,0x020}, {rbp,0x018}, {rsi,0x010}, {rdi,0x008}, {r12,0x000}, {-1,-1} }}, { 0x0a, 0x50, TRUE, 0x058, { {rsp,0x060}, {rbx,0x050}, {rbp,0x048}, {rsi,0x040}, {rdi,0x038}, {r12,0x030}, {-1,-1} }}, + { 0x0c, 0x50, FALSE, 0x058, { {rsp,0x060}, {rbx,0x050}, {rbp,0x048}, {rsi,0x040}, {rdi,0x038}, {r12,0x030}, {-1,-1} }}, + { 0x10, 0x50, FALSE, 0x028, { {rsp,0x030}, {rbx,0x020}, {rbp,0x018}, {rsi,0x010}, {rdi,0x008}, {r12,0x000}, {-1,-1} }}, + { 0x12, 0x50, FALSE, 0x020, { {rsp,0x028}, {rbx,0x018}, {rbp,0x010}, {rsi,0x008}, {rdi,0x000}, {-1,-1} }}, + { 0x13, 0x50, FALSE, 0x018, { {rsp,0x020}, {rbx,0x010}, {rbp,0x008}, {rsi,0x000}, {-1,-1} }}, + { 0x14, 0x50, FALSE, 0x010, { {rsp,0x018}, {rbx,0x008}, {rbp,0x000}, {-1,-1} }}, + { 0x15, 0x50, FALSE, 0x008, { {rsp,0x010}, {rbx,0x000}, {-1,-1} }}, + { 0x16, 0x50, FALSE, 0x000, { {rsp,0x008}, {-1,-1} }}, }; static const struct unwind_test tests[] =