[PATCH v2 0/2] MR9390: ntdll: Don't detect epilogue in chained unwind function on x64.
Fixes a regression introduced by 052722667c9dbf96a5dc04d45af55599718d9f92. -- v2: ntdll: Check for related functions in is_inside_epilog() when detecting tail call. ntdll: Don't detect epilogue in chained unwind function on x64. https://gitlab.winehq.org/wine/wine/-/merge_requests/9390
From: Paul Gofman <pgofman(a)codeweavers.com> Fixes a regression introduced by 052722667c9dbf96a5dc04d45af55599718d9f92. --- dlls/ntdll/tests/unwind.c | 125 +++++++++++++++++++++++++++++++++++++- dlls/ntdll/unwind.c | 5 +- 2 files changed, 125 insertions(+), 5 deletions(-) diff --git a/dlls/ntdll/tests/unwind.c b/dlls/ntdll/tests/unwind.c index 7255e2fc52e..291690ada78 100644 --- a/dlls/ntdll/tests/unwind.c +++ b/dlls/ntdll/tests/unwind.c @@ -2703,7 +2703,7 @@ static void call_virtual_unwind_x86( int testnum, const struct unwind_test_x86 * NTSTATUS status; CONTEXT context; PEXCEPTION_ROUTINE handler; - RUNTIME_FUNCTION runtime_func; + RUNTIME_FUNCTION runtime_func, *chained_func = NULL; KNONVOLATILE_CONTEXT_POINTERS ctx_ptr; UINT i, j, k, broken_k; ULONG64 fake_stack[256]; @@ -2714,13 +2714,33 @@ static void call_virtual_unwind_x86( int testnum, const struct unwind_test_x86 * if (test->unwind_info) { UINT handler_offset = 4 + 2 * test->unwind_info[2]; + const BYTE *chained_info; UINT unwind_size; handler_offset = (handler_offset + 3) & ~3; + if (test->unwind_info[0] & (UNW_FLAG_CHAININFO << 3)) + { + chained_func = (RUNTIME_FUNCTION *)((char *)code_mem + unwind_offset + handler_offset); + chained_info = test->unwind_info + handler_offset + sizeof(RUNTIME_FUNCTION); + handler_offset += sizeof(RUNTIME_FUNCTION) + 4 + 2 * chained_info[2]; + handler_offset = (handler_offset + 3) & ~3; + } unwind_size = handler_offset + 8; memcpy( (char *)code_mem + unwind_offset, test->unwind_info, unwind_size ); - runtime_func.BeginAddress = code_offset; - runtime_func.EndAddress = code_offset + test->function_size; + if (chained_func) + { + runtime_func.BeginAddress = code_offset + chained_func->BeginAddress; + runtime_func.EndAddress = code_offset + chained_func->EndAddress; + + chained_func->EndAddress = code_offset + chained_func->BeginAddress; + chained_func->BeginAddress = code_offset; + chained_func->UnwindData = unwind_offset + (chained_info - test->unwind_info); + } + else + { + runtime_func.BeginAddress = code_offset; + runtime_func.EndAddress = code_offset + test->function_size; + } runtime_func.UnwindData = unwind_offset; } @@ -3160,6 +3180,104 @@ static void test_virtual_unwind_x86(void) { 0x03, 0x00, FALSE, 0x008, 0x000, { {rsp,0x010}, {rbp,0x000}, {-1,-1} }}, }; + static const BYTE function_7[] = + { + 0x48, 0x83, 0xec, 0x30, /* 00: sub $0x30,%rsp */ + 0x48, 0x89, 0x5c, 0x24, 0x10, /* 04: mov %rbx,0x10(%rsp) */ + 0x90, /* 09: nop */ + 0xe9, 0x0b, 0x00, 0x00, 0x00, /* 0a: jmp chained */ + 0x90, /* 0f: nop */ + 0x48, 0x8b, 0x5c, 0x24, 0x10, /* 10: mov 0x10(%rsp),%rbx */ + 0x48, 0x83, 0xc4, 0x30, /* 15: add $0x30,%rsp */ + 0xc3, /* 19: ret */ + /* chained: */ + 0x55, /* 00: / 1a: push %rbp */ + 0x48, 0x83, 0xec, 0x50, /* 01: / 1b: sub $0x50,%rsp */ + 0x48, 0x8d, 0x6c, 0x24, 0x30, /* 05: / 1f: lea 0x30(%rsp),%rbp */ + 0x48, 0x89, 0x5d, 0x10, /* 0a: / 24: mov %rbx,0x10(%rbp) */ + 0xe9, 0xe3, 0xff, 0xff, 0xff, /* 0e: / 28: jmp 10 */ + 0xe9, 0x00, 0x00, 0x00, 0x00, /* 13: / 2d: jmp 18 */ + 0x90, /* 18: / 32: nop */ + 0x48, 0x8b, 0x5d, 0x10, /* 19: / 33: mov 0x10(%rbp),%rbx */ + 0x48, 0x83, 0xc4, 0x50, /* 1d: / 37: add $0x50,%rsp */ + 0x5d, /* 21: / 3b: pop %rbp */ + 0xc3, /* 22: / 3c: ret */ + }; + C_ASSERT(sizeof(function_7) == 0x3d); + + static const BYTE unwind_info_7[] = + { + 1 | (UNW_FLAG_CHAININFO << 3), /* version + flags */ + 0x0e, /* prolog size */ + 5, /* opcode count */ + (0x03 << 4) | rbp, /* frame reg rbp offset 0x30 */ + + 0x0e, UWOP(SAVE_NONVOL, rbx), 0x2, 0, /* 16: mov %rbx,0x10(%rbp) */ + 0x0f, UWOP(SET_FPREG, rbp), /* 0f: lea 0x30(%rsp),rbp */ + 0x05, UWOP(ALLOC_SMALL, 5), /* 0a: sub $0x30,%rsp */ + 0x01, UWOP(PUSH_NONVOL, rbp), /* 03: push %rbp */ + + /* align */ + 0x00, 0x00, + + /* chained runtime function, adjusted in test code */ + 0x1a, 0x00, 0x00, 0x00, /* inner function BeginAddress offset from the whole function start */ + 0x3d, 0x00, 0x00, 0x00, /* inner function EndAddress offset from the whole function start */ + 0x00, 0x00, 0x00, 0x00, /* UnwindData */ + + /* chained unwind data */ + 1, /* version + flags */ + 0x09, /* prolog size */ + 3, /* opcode count */ + 0, /* frame reg rbp offset 0x30 */ + + 0x09, UWOP(SAVE_NONVOL, rbx), 0x2, 0, /* 16: mov %rbx,0x10(%rsp) */ + 0x04, UWOP(ALLOC_SMALL, 5), /* 0a: sub $0x30,%rsp */ + + /* align */ + 0x00, 0x00, + + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + }; + + static const struct results_x86 results_7[] = + { + /* offset rbp handler rip frame registers */ + { 0x1a, 0x30, FALSE, 0x030, 0x000, { {rsp,0x038}, {rbx,0x010}, {-1,-1} }}, + { 0x1b, 0x30, FALSE, 0x038, 0x000, { {rsp,0x040}, {rbx,0x010}, {rbp,0x000}, {-1,-1} }}, + { 0x1f, 0x30, FALSE, 0x068, 0x000, { {rsp,0x070}, {rbx,0x010}, {rbp,0x030}, {-1,-1} }}, + { 0x24, 0x30, FALSE, 0x068, 0x000, { {rsp,0x070}, {rbx,0x010}, {rbp,0x030}, {-1,-1} }}, + + /* jump to chained function, handled as tail jump or immediate return */ + { 0x28, 0x30, FALSE, 0x000, 0x000, { {rsp,0x008}, {-1,-1} }}, + /* jump inside inner function, no special handling */ + { 0x2d, 0x30, FALSE, 0x068, 0x000, { {rsp,0x070}, {rbx,0x010}, {rbp,0x030}, {-1,-1} }}, + + { 0x32, 0x30, FALSE, 0x068, 0x000, { {rsp,0x070}, {rbx,0x010}, {rbp,0x030}, {-1,-1} }}, + { 0x33, 0x30, FALSE, 0x068, 0x000, { {rsp,0x070}, {rbx,0x010}, {rbp,0x030}, {-1,-1} }}, + { 0x37, 0x30, FALSE, 0x058, 0x058, { {rsp,0x060}, {rbp,0x050}, {-1,-1} }}, + { 0x3b, 0x30, FALSE, 0x008, 0x008, { {rsp,0x010}, {rbp,0x000}, {-1,-1} }}, + { 0x3c, 0x30, FALSE, 0x000, 0x000, { {rsp,0x008}, {-1,-1} }}, + }; + + static const struct results_x86 broken_results_7[] = + { + /* offset rbp handler rip frame registers */ + { 0x1a, 0x30, FALSE, 0x030, 0x000, { {rsp,0x038}, {rbx,0x010}, {-1,-1} }}, + { 0x1b, 0x30, FALSE, 0x038, 0x000, { {rsp,0x040}, {rbx,0x010}, {rbp,0x000}, {-1,-1} }}, + { 0x1f, 0x30, FALSE, 0x068, 0x000, { {rsp,0x070}, {rbx,0x010}, {rbp,0x030}, {-1,-1} }}, + { 0x24, 0x30, FALSE, 0x068, 0x000, { {rsp,0x070}, {rbx,0x010}, {rbp,0x030}, {-1,-1} }}, + { 0x28, 0x30, FALSE, 0x000, 0x000, { {rsp,0x008}, {-1,-1} }}, + { 0x2d, 0x30, FALSE, 0x068, 0x000, { {rsp,0x070}, {rbx,0x010}, {rbp,0x030}, {-1,-1} }}, + { 0x32, 0x30, FALSE, 0x068, 0x000, { {rsp,0x070}, {rbx,0x010}, {rbp,0x030}, {-1,-1} }}, + { 0x33, 0x30, FALSE, 0x068, 0x000, { {rsp,0x070}, {rbx,0x010}, {rbp,0x030}, {-1,-1} }}, + /* Before Win11 output frame in epilogue is set with fpreg even after it is popped. */ + { 0x37, 0x30, FALSE, 0x058, 0x000, { {rsp,0x060}, {rbp,0x050}, {-1,-1} }}, + { 0x3b, 0x30, FALSE, 0x008, 0x000, { {rsp,0x010}, {rbp,0x000}, {-1,-1} }}, + { 0x3c, 0x30, FALSE, 0x000, 0x000, { {rsp,0x008}, {-1,-1} }}, + }; + static const struct unwind_test_x86 tests[] = { { function_0, sizeof(function_0), unwind_info_0, results_0, ARRAY_SIZE(results_0), broken_results_0 }, @@ -3175,6 +3293,7 @@ static void test_virtual_unwind_x86(void) { function_6_1, sizeof(function_6_1), unwind_info_6, results_6_epilogue, ARRAY_SIZE(results_6_epilogue) }, { function_6_2, sizeof(function_6_2), unwind_info_6, results_6_body, ARRAY_SIZE(results_6_body) }, { function_6_3, sizeof(function_6_3), unwind_info_6, results_6_epilogue, ARRAY_SIZE(results_6_epilogue) }, + { function_7, sizeof(function_7), unwind_info_7, results_7, ARRAY_SIZE(results_7), broken_results_7 }, }; struct unwind_test_x86 jump_test = { function_tail_jump_ff, sizeof(function_tail_jump_ff), unwind_info_6, diff --git a/dlls/ntdll/unwind.c b/dlls/ntdll/unwind.c index 1971d08c478..baf9936dce4 100644 --- a/dlls/ntdll/unwind.c +++ b/dlls/ntdll/unwind.c @@ -2019,7 +2019,7 @@ NTSTATUS WINAPI RtlVirtualUnwind2( ULONG type, ULONG_PTR base, ULONG_PTR pc, ULONG64 frame, off; struct UNWIND_INFO *info; unsigned int i, prolog_offset; - BOOL mach_frame = FALSE; + BOOL mach_frame = FALSE, chained = FALSE; #ifdef __arm64ec__ if (RtlIsEcCode( pc )) @@ -2078,7 +2078,7 @@ NTSTATUS WINAPI RtlVirtualUnwind2( ULONG type, ULONG_PTR base, ULONG_PTR pc, { prolog_offset = ~0; /* Since Win10 1809 epilogue does not have a special treatment in case of zero opcode count. */ - if (info->count && is_inside_epilog( (BYTE *)pc, base, function )) + if (!chained && info->count && is_inside_epilog( (BYTE *)pc, base, function )) { TRACE("inside epilog.\n"); interpret_epilog( (BYTE *)pc, context, ctx_ptr ); @@ -2154,6 +2154,7 @@ NTSTATUS WINAPI RtlVirtualUnwind2( ULONG type, ULONG_PTR base, ULONG_PTR pc, if (!(info->flags & UNW_FLAG_CHAININFO)) break; function = &handler_data->chain; /* restart with the chained info */ + chained = TRUE; } if (!mach_frame) -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/9390
From: Paul Gofman <pgofman(a)codeweavers.com> Fixes a regression introduced by 052722667c9dbf96a5dc04d45af55599718d9f92. --- dlls/ntdll/tests/unwind.c | 104 +++++++++++++++++++++++++++++++++++--- dlls/ntdll/unwind.c | 30 ++++++++++- 2 files changed, 125 insertions(+), 9 deletions(-) diff --git a/dlls/ntdll/tests/unwind.c b/dlls/ntdll/tests/unwind.c index 291690ada78..a60db1bc70f 100644 --- a/dlls/ntdll/tests/unwind.c +++ b/dlls/ntdll/tests/unwind.c @@ -2695,9 +2695,10 @@ static const char * const reg_names_x86[16] = #define UWOP(code,info) (UWOP_##code | ((info) << 4)) +static const int code_offset = 1024; + static void call_virtual_unwind_x86( int testnum, const struct unwind_test_x86 *test ) { - static const int code_offset = 1024; static const int unwind_offset = 2048; void *data; NTSTATUS status; @@ -3278,6 +3279,43 @@ static void test_virtual_unwind_x86(void) { 0x3c, 0x30, FALSE, 0x000, 0x000, { {rsp,0x008}, {-1,-1} }}, }; + static const struct results_x86 results_7_chain_jmp_detected[] = + { + /* offset rbp handler rip frame registers */ + { 0x1a, 0x30, FALSE, 0x030, 0x000, { {rsp,0x038}, {rbx,0x010}, {-1,-1} }}, + { 0x1b, 0x30, FALSE, 0x038, 0x000, { {rsp,0x040}, {rbx,0x010}, {rbp,0x000}, {-1,-1} }}, + { 0x1f, 0x30, FALSE, 0x068, 0x000, { {rsp,0x070}, {rbx,0x010}, {rbp,0x030}, {-1,-1} }}, + { 0x24, 0x30, FALSE, 0x068, 0x000, { {rsp,0x070}, {rbx,0x010}, {rbp,0x030}, {-1,-1} }}, + + /* jump to chained function, no special handling */ + { 0x2d, 0x30, FALSE, 0x068, 0x000, { {rsp,0x070}, {rbx,0x010}, {rbp,0x030}, {-1,-1} }}, + /* jump inside inner function, no special handling */ + { 0x2d, 0x30, FALSE, 0x068, 0x000, { {rsp,0x070}, {rbx,0x010}, {rbp,0x030}, {-1,-1} }}, + + { 0x32, 0x30, FALSE, 0x068, 0x000, { {rsp,0x070}, {rbx,0x010}, {rbp,0x030}, {-1,-1} }}, + { 0x33, 0x30, FALSE, 0x068, 0x000, { {rsp,0x070}, {rbx,0x010}, {rbp,0x030}, {-1,-1} }}, + { 0x37, 0x30, FALSE, 0x058, 0x058, { {rsp,0x060}, {rbp,0x050}, {-1,-1} }}, + { 0x3b, 0x30, FALSE, 0x008, 0x008, { {rsp,0x010}, {rbp,0x000}, {-1,-1} }}, + { 0x3c, 0x30, FALSE, 0x000, 0x000, { {rsp,0x008}, {-1,-1} }}, + }; + + static const struct results_x86 broken_results_7_chain_jmp_detected[] = + { + /* offset rbp handler rip frame registers */ + { 0x1a, 0x30, FALSE, 0x030, 0x000, { {rsp,0x038}, {rbx,0x010}, {-1,-1} }}, + { 0x1b, 0x30, FALSE, 0x038, 0x000, { {rsp,0x040}, {rbx,0x010}, {rbp,0x000}, {-1,-1} }}, + { 0x1f, 0x30, FALSE, 0x068, 0x000, { {rsp,0x070}, {rbx,0x010}, {rbp,0x030}, {-1,-1} }}, + { 0x24, 0x30, FALSE, 0x068, 0x000, { {rsp,0x070}, {rbx,0x010}, {rbp,0x030}, {-1,-1} }}, + { 0x2d, 0x30, FALSE, 0x068, 0x000, { {rsp,0x070}, {rbx,0x010}, {rbp,0x030}, {-1,-1} }}, + { 0x2d, 0x30, FALSE, 0x068, 0x000, { {rsp,0x070}, {rbx,0x010}, {rbp,0x030}, {-1,-1} }}, + { 0x32, 0x30, FALSE, 0x068, 0x000, { {rsp,0x070}, {rbx,0x010}, {rbp,0x030}, {-1,-1} }}, + { 0x33, 0x30, FALSE, 0x068, 0x000, { {rsp,0x070}, {rbx,0x010}, {rbp,0x030}, {-1,-1} }}, + /* Before Win11 output frame in epilogue is set with fpreg even after it is popped. */ + { 0x37, 0x30, FALSE, 0x058, 0x000, { {rsp,0x060}, {rbp,0x050}, {-1,-1} }}, + { 0x3b, 0x30, FALSE, 0x008, 0x000, { {rsp,0x010}, {rbp,0x000}, {-1,-1} }}, + { 0x3c, 0x30, FALSE, 0x000, 0x000, { {rsp,0x008}, {-1,-1} }}, + }; + static const struct unwind_test_x86 tests[] = { { function_0, sizeof(function_0), unwind_info_0, results_0, ARRAY_SIZE(results_0), broken_results_0 }, @@ -3296,10 +3334,12 @@ static void test_virtual_unwind_x86(void) { function_7, sizeof(function_7), unwind_info_7, results_7, ARRAY_SIZE(results_7), broken_results_7 }, }; - struct unwind_test_x86 jump_test = { function_tail_jump_ff, sizeof(function_tail_jump_ff), unwind_info_6, + struct unwind_test_x86 test_data = { function_tail_jump_ff, sizeof(function_tail_jump_ff), unwind_info_6, results_6_epilogue, ARRAY_SIZE(results_6_epilogue) }; unsigned int i, rex_prefix, ind; + RUNTIME_FUNCTION rtf; + BOOL bret; for (rex_prefix = 0; rex_prefix <= 8; ++rex_prefix) { @@ -3317,24 +3357,74 @@ static void test_virtual_unwind_x86(void) if (((rex_prefix == 8 || !rex_prefix) && i == 0x25) || (rex_prefix == 8 && ext == 4)) { - jump_test.results = results_6_epilogue; - jump_test.nb_results = ARRAY_SIZE(results_6_epilogue); + test_data.results = results_6_epilogue; + test_data.nb_results = ARRAY_SIZE(results_6_epilogue); expected = 1; } else { - jump_test.results = results_6_body; - jump_test.nb_results = ARRAY_SIZE(results_6_body); + test_data.results = results_6_body; + test_data.nb_results = ARRAY_SIZE(results_6_body); expected = 0; } winetest_push_context( "rex %#x, byte %#x, expected %d", rex_prefix, i, expected ); - call_virtual_unwind_x86( 0, &jump_test ); + call_virtual_unwind_x86( 0, &test_data ); winetest_pop_context(); } } for (i = 0; i < ARRAY_SIZE(tests); i++) call_virtual_unwind_x86( i, &tests[i] ); + + /* jmp is out of the current function but jump destination function has the same start address */ + rtf.BeginAddress = code_offset; + rtf.EndAddress = code_offset + sizeof(function_6_1) + 1; + rtf.UnwindData = 0; + bret = RtlAddFunctionTable( &rtf, 1, (ULONG_PTR)code_mem ); + ok( bret, "RtlAddFunctionTable failed.\n" ); + test_data.function = function_6_1; + test_data.function_size = sizeof(function_6_1); + test_data.unwind_info = unwind_info_6; + test_data.results = results_6_body; + test_data.nb_results = ARRAY_SIZE(results_6_body); + test_data.broken_results = results_6_epilogue; /* before Win10 2009. */ + winetest_push_context( "line %d", __LINE__ ); + call_virtual_unwind_x86( 0, &test_data ); + winetest_pop_context(); + bret = RtlDeleteFunctionTable( &rtf ); + ok( bret, "RtlDeleteFunctionTable failed.\n" ); + + /* jump destination is in a function which range covers our function but the start address is different */ + rtf.BeginAddress = code_offset - 1; + rtf.EndAddress = code_offset + sizeof(function_6_1) + 1; + rtf.UnwindData = 0; + bret = RtlAddFunctionTable( &rtf, 1, (ULONG_PTR)code_mem ); + ok( bret, "RtlAddFunctionTable failed.\n" ); + test_data.results = results_6_epilogue; + test_data.nb_results = ARRAY_SIZE(results_6_epilogue); + winetest_push_context( "line %d", __LINE__ ); + call_virtual_unwind_x86( 0, &test_data ); + winetest_pop_context(); + bret = RtlDeleteFunctionTable( &rtf ); + ok( bret, "RtlDeleteFunctionTable failed.\n" ); + + /* jump destination is in a chained function */ + rtf.BeginAddress = code_offset; + rtf.EndAddress = code_offset + 0x1a; + rtf.UnwindData = 0; + bret = RtlAddFunctionTable( &rtf, 1, (ULONG_PTR)code_mem ); + ok( bret, "RtlAddFunctionTable failed.\n" ); + test_data.function = function_7; + test_data.function_size = sizeof(function_7); + test_data.unwind_info = unwind_info_7; + test_data.results = results_7_chain_jmp_detected; + test_data.nb_results = ARRAY_SIZE(results_7_chain_jmp_detected); + test_data.broken_results = broken_results_7_chain_jmp_detected; + winetest_push_context( "line %d", __LINE__ ); + call_virtual_unwind_x86( 0, &test_data ); + winetest_pop_context(); + bret = RtlDeleteFunctionTable( &rtf ); + ok( bret, "RtlDeleteFunctionTable failed.\n" ); } #endif /* __x86_64__ */ diff --git a/dlls/ntdll/unwind.c b/dlls/ntdll/unwind.c index baf9936dce4..6c18b58b7a8 100644 --- a/dlls/ntdll/unwind.c +++ b/dlls/ntdll/unwind.c @@ -1843,6 +1843,32 @@ static int get_opcode_size( struct opcode op ) } } +static void *get_main_function_start( const RUNTIME_FUNCTION *f, ULONG_PTR base ) +{ + const struct UNWIND_INFO *info; + const union handler_data *data; + + while (f->UnwindData) + { + info = (const struct UNWIND_INFO *)((char *)base + f->UnwindData); + if (!(info->flags & UNW_FLAG_CHAININFO)) break; + + data = (union handler_data *)&info->opcodes[(info->count + 1) & ~1]; + f = &data->chain; + } + return (char *)base + f->BeginAddress; +} + +static BOOL is_address_in_function( BYTE *pc, ULONG64 base, const RUNTIME_FUNCTION *function ) +{ + RUNTIME_FUNCTION *f; + ULONG_PTR pc_base; + + if (pc - (BYTE *)base >= function->BeginAddress && pc - (BYTE *)base < function->EndAddress) return TRUE; + if (!(f = RtlLookupFunctionEntry( (ULONG_PTR)pc, &pc_base, NULL ))) return FALSE; + return get_main_function_start( f, pc_base ) == get_main_function_start( function, base ); +} + static BOOL is_inside_epilog( BYTE *pc, ULONG64 base, const RUNTIME_FUNCTION *function ) { /* add or lea must be the first instruction, and it must have a rex.W prefix */ @@ -1907,10 +1933,10 @@ static BOOL is_inside_epilog( BYTE *pc, ULONG64 base, const RUNTIME_FUNCTION *fu return TRUE; case 0xe9: /* jmp nnnn */ pc += 5 + *(LONG *)(pc + 1); - return !(pc - (BYTE *)base >= function->BeginAddress && pc - (BYTE *)base < function->EndAddress); + return !is_address_in_function( pc, base, function ); case 0xeb: /* jmp n */ pc += 2 + (signed char)pc[1]; - return !(pc - (BYTE *)base >= function->BeginAddress && pc - (BYTE *)base < function->EndAddress); + return !is_address_in_function( pc, base, function ); case 0xf3: /* rep; ret (for amd64 prediction bug) */ return pc[1] == 0xc3; case 0xff: /* jmp */ -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/9390
v2: - add patch fixing a bit different case for tail call detection, when the jump is not immediately related function but which share common "main" function as the last chained function. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/9390#note_121195
participants (2)
-
Paul Gofman -
Paul Gofman (@gofman)