From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: (qmail 15784 invoked by alias); 15 Jun 2012 08:10:21 -0000 Received: (qmail 15361 invoked by uid 22791); 15 Jun 2012 08:10:13 -0000 X-SWARE-Spam-Status: No, hits=-1.8 required=5.0 tests=AWL,BAYES_00,TW_BJ,TW_QW X-Spam-Check-By: sourceware.org Received: from mel.act-europe.fr (HELO mel.act-europe.fr) (194.98.77.210) by sourceware.org (qpsmtpd/0.43rc1) with ESMTP; Fri, 15 Jun 2012 08:09:58 +0000 Received: from localhost (localhost [127.0.0.1]) by filtered-smtp.eu.adacore.com (Postfix) with ESMTP id 1EBC0290046; Fri, 15 Jun 2012 10:10:03 +0200 (CEST) Received: from mel.act-europe.fr ([127.0.0.1]) by localhost (smtp.eu.adacore.com [127.0.0.1]) (amavisd-new, port 10024) with ESMTP id 6sTCFYoPMV7P; Fri, 15 Jun 2012 10:10:03 +0200 (CEST) Received: from ulanbator.act-europe.fr (ulanbator.act-europe.fr [10.10.1.67]) (using TLSv1 with cipher AES128-SHA (128/128 bits)) (No client certificate requested) by mel.act-europe.fr (Postfix) with ESMTP id 005C3290014; Fri, 15 Jun 2012 10:10:03 +0200 (CEST) From: Tristan Gingold Content-Type: text/plain; charset=us-ascii Content-Transfer-Encoding: quoted-printable Subject: [RFA] Add Windows x64 SEH unwinder Date: Fri, 15 Jun 2012 08:10:00 -0000 Message-Id: Cc: Kai Tietz To: "gdb-patches@sourceware.org ml" Mime-Version: 1.0 (Apple Message framework v1278) X-IsSubscribed: yes Mailing-List: contact gdb-patches-help@sourceware.org; run by ezmlm Precedence: bulk List-Id: List-Subscribe: List-Archive: List-Post: List-Help: , Sender: gdb-patches-owner@sourceware.org X-SW-Source: 2012-06/txt/msg00502.txt.bz2 Hi, the Windows x64 ABI specifies unwind data info in order to unwind frames fo= r propagating exceptions or getting back traces. GCC emits this infos since version 4.7 This patch adds an unwinder that reads these data. The main advantage is t= hat gdb is now able to unwind through code compiled with other compilers (a= nd through system libraries). I haven't run the gdb testsuite on Windows x64 (I don't know if this is doa= ble), but I have manually tested it on a few executables, including gdb its= elf. Comments are welcome. Tristan. 2012-06-15 Tristan Gingold * amd64-windows-tdep.c (struct x64_frame_cache): Declare. (x64_w2gdb_regnum): New array. (x64_frame_decode_epilogue, x64_frame_decode_insns) (x64_frame_cache, x64_frame_prev_register, x64_frame_this_id): New functions. (x64_frame_unwind): New variable. (amd64_windows_init_abi): Register this unwinder. diff --git a/gdb/amd64-windows-tdep.c b/gdb/amd64-windows-tdep.c index 4a40f47..7070e06 100644 --- a/gdb/amd64-windows-tdep.c +++ b/gdb/amd64-windows-tdep.c @@ -23,6 +23,12 @@ #include "gdbtypes.h" #include "gdbcore.h" #include "regcache.h" +#include "objfiles.h" +#include "frame-unwind.h" +#include "coff/internal.h" +#include "coff/i386.h" +#include "coff/pe.h" +#include "libcoff.h" =20 /* The registers used to pass integer arguments during a function call. */ static int amd64_windows_dummy_call_integer_regs[] =3D @@ -153,6 +159,588 @@ amd64_skip_main_prologue (struct gdbarch *gdbarch, CO= RE_ADDR pc) return pc; } =20 +struct x64_frame_cache +{ + CORE_ADDR image_base; /* ImageBase for the module. */ + CORE_ADDR start_address; /* Function start rva. */ + CORE_ADDR pc; /* Next instruction to be executed. */ + CORE_ADDR sp; /* Current sp. */ + + CORE_ADDR prev_reg_addr[16]; /* Address of saved registers. */ + CORE_ADDR prev_xmm_addr[16]; /* Likewise for xmm registers. */ + /* These two next fields are set only for machine info frames. */ + CORE_ADDR prev_rip_addr; /* Likewise for RIP. */ + CORE_ADDR prev_rsp_addr; /* Likewise for RSP. */ + CORE_ADDR prev_sp; /* Address of the previous frame. */ +}; + +/* Convert a Windows register number to gdb. */ +static const enum amd64_regnum x64_w2gdb_regnum[] =3D +{ + AMD64_RAX_REGNUM, + AMD64_RCX_REGNUM, + AMD64_RDX_REGNUM, + AMD64_RBX_REGNUM, + AMD64_RSP_REGNUM, + AMD64_RBP_REGNUM, + AMD64_RSI_REGNUM, + AMD64_RDI_REGNUM, + AMD64_R8_REGNUM, + AMD64_R9_REGNUM, + AMD64_R10_REGNUM, + AMD64_R11_REGNUM, + AMD64_R12_REGNUM, + AMD64_R13_REGNUM, + AMD64_R14_REGNUM, + AMD64_R15_REGNUM +}; + +/* Try to recognize and decode an epilogue sequence. */ + +static int +x64_frame_decode_epilogue (struct frame_info *this_frame, + struct x64_frame_cache *cache) +{ + /* Not in a prologue, so maybe in an epilogue. For the rules, cf + http://msdn.microsoft.com/en-us/library/tawsa7cb.aspx + Furthermore, according to RtlVirtualUnwind, the complete list of + epilog marker is: + - ret [c3] + - ret n [c2 imm16] + - rep ret [f3 c3] + - jmp imm8 | imm32 [eb rel8] or [e9 rel32] + - jmp qword ptr imm32 - not handled + - rex jmp reg [4X ff eY] + I would add: + - pop reg [41 58-5f] or [58-5f] + + We don't care about the instruction that deallocate the frame: + if it hasn't been executed, we can safely decode the insns, + if it has been executed, the following epilog decoding will + work. + */ + CORE_ADDR pc =3D cache->pc; + CORE_ADDR cur_sp =3D cache->sp; + struct gdbarch *gdbarch =3D get_frame_arch (this_frame); + enum bfd_endian byte_order =3D gdbarch_byte_order (gdbarch); + + while (1) + { + gdb_byte op; + gdb_byte rex; + + if (target_read_memory (pc, &op, 1) !=3D 0) + return -1; + + if (op =3D=3D 0xc3) + { + /* Ret. */ + cache->prev_rip_addr =3D cur_sp; + cache->prev_sp =3D cur_sp + 8; + return 1; + } + else if (op =3D=3D 0xeb) + { + /* jmp rel8 */ + gdb_byte rel8; + + if (target_read_memory (pc + 1, &rel8, 1) !=3D 0) + return -1; + pc =3D pc + 2 + (signed char)rel8; + } + else if (op =3D=3D 0xec) + { + /* jmp rel32 */ + gdb_byte rel32[4]; + + if (target_read_memory (pc + 1, rel32, 4) !=3D 0) + return -1; + pc =3D pc + 5 + extract_signed_integer (rel32, 4, byte_order); + } + else if (op >=3D 0x58 && op <=3D 0x5f) + { + /* pop reg */ + cache->prev_reg_addr[x64_w2gdb_regnum[op & 0x0f]] =3D cur_sp; + cur_sp +=3D 8; + } + else if (op =3D=3D 0xc2) + { + /* ret n */ + gdb_byte imm16[2]; + + if (target_read_memory (pc + 1, imm16, 2) !=3D 0) + return -1; + cache->prev_rip_addr =3D cur_sp; + cache->prev_sp =3D cur_sp + + extract_unsigned_integer (imm16, 4, byte_order); + return 1; + } + else if (op =3D=3D 0xf3) + { + /* rep; ret */ + gdb_byte op1; + + if (target_read_memory (pc + 2, &op1, 1) !=3D 0) + return -1; + if (op1 !=3D 0xc3) + return 0; + + cache->prev_rip_addr =3D cur_sp; + cache->prev_sp =3D cur_sp + 8; + return 1; + } + else if (op < 0x40 || op > 0x4f) + { + /* Not REX, so unknown. */ + return 0; + } + + /* Got a REX prefix, read next byte. */ + rex =3D op; + if (target_read_memory (pc + 1, &op, 1) !=3D 0) + return -1; + + if (op >=3D 0x58 && op <=3D 0x5f) + { + /* pop reg */ + unsigned int reg; + + reg =3D (op & 0x0f) | ((rex & 1) << 3); + cache->prev_reg_addr[x64_w2gdb_regnum[reg]] =3D cur_sp; + cur_sp +=3D 8; + } + else if (op =3D=3D 0xff) + { + /* rex jmp reg */ + gdb_byte op1; + unsigned int reg; + gdb_byte buf[8]; + + if (target_read_memory (pc + 2, &op1, 1) !=3D 0) + return -1; + if ((op1 & 0xf8) !=3D 0xe0) + return 0; + reg =3D (op1 & 0x0f) | ((rex & 1) << 3); + + get_frame_register (this_frame, x64_w2gdb_regnum[reg], buf); + pc =3D extract_unsigned_integer (buf, 8, byte_order); + } + else + return 0; + + /* Allow the user to break this loop. */ + if (quit_flag) + return 0; + } +} + +/* Decode and execute unwind insns at UNWIND_INFO. */ + +static void +x64_frame_decode_insns (struct frame_info *this_frame, + struct x64_frame_cache *cache, + CORE_ADDR unwind_info) +{ + CORE_ADDR save_addr =3D 0; + CORE_ADDR cur_sp =3D cache->sp; + struct gdbarch *gdbarch =3D get_frame_arch (this_frame); + enum bfd_endian byte_order =3D gdbarch_byte_order (gdbarch); + int j; + + for (j =3D 0; ; j++) + { + struct external_pex64_unwind_info ex_ui; + gdb_byte insns[2 * 256]; + gdb_byte *p; + gdb_byte *end_insns; + unsigned char codes_count; + unsigned char frame_reg; + unsigned char frame_off; + + /* Read and decode header. */ + if (target_read_memory (cache->image_base + unwind_info, + (char *) &ex_ui, sizeof (ex_ui)) !=3D 0) + return; + + if (unwind_debug) + fprintf_unfiltered + (gdb_stdlog, + "x64_frame_play_insn: " + "%s: ver: %02x, plgsz: %02x, cnt: %02x, frame: %02x\n", + paddress (gdbarch, unwind_info), + ex_ui.Version_Flags, ex_ui.SizeOfPrologue, + ex_ui.CountOfCodes, ex_ui.FrameRegisterOffset); + + /* Check version. */ + if (PEX64_UWI_VERSION (ex_ui.Version_Flags) !=3D 1) + return; + + if (j =3D=3D 0 + && (cache->pc >=3D + cache->image_base + cache->start_address + ex_ui.SizeOfPrologue)) + { + /* Not in the prologue; try to decode an epilog. */ + if (x64_frame_decode_epilogue (this_frame, cache) =3D=3D 1) + return; + + /* Not in an epilog. Clear possible side effects. */ + memset (cache->prev_reg_addr, 0, sizeof (cache->prev_reg_addr)); + } + + codes_count =3D ex_ui.CountOfCodes; + frame_reg =3D PEX64_UWI_FRAMEREG (ex_ui.FrameRegisterOffset); + + if (frame_reg !=3D 0) + { + /* According to msdn: + If an FP reg is used, then any unwind code taking an offset must + only be used the the FP reg is established in the prolog. */ + gdb_byte buf[8]; + int frreg =3D x64_w2gdb_regnum[frame_reg]; + + get_frame_register (this_frame, frreg, buf); + save_addr =3D extract_unsigned_integer (buf, 8, byte_order); + + if (unwind_debug) + fprintf_unfiltered (gdb_stdlog, " frame_reg=3D%s, val=3D%s\n", + gdbarch_register_name (gdbarch, frreg), + paddress (gdbarch, save_addr)); + } + + /* Read opcodes. */ + if (codes_count !=3D 0 + && target_read_memory (cache->image_base + unwind_info + + sizeof (ex_ui), + insns, codes_count * 2) !=3D 0) + return; + + end_insns =3D &insns[codes_count * 2]; + for (p =3D insns; p < end_insns; p +=3D 2) + { + int reg; + + if (unwind_debug) + fprintf_unfiltered + (gdb_stdlog, " op #%u: off=3D0x%02x, insn=3D0x%02x\n", + (unsigned) (p - insns), p[0], p[1]); + + /* Virtually execute the operation. */ + if (cache->pc >=3D cache->image_base + cache->start_address + p[0]) + { + /* If there is no frame registers defined, the current value of + rsp is used instead. */ + if (frame_reg =3D=3D 0) + save_addr =3D cur_sp; + + switch (PEX64_UNWCODE_CODE (p[1])) + { + case UWOP_PUSH_NONVOL: + /* Push pre-decrements RSP. */ + reg =3D x64_w2gdb_regnum[PEX64_UNWCODE_INFO (p[1])]; + cache->prev_reg_addr[reg] =3D cur_sp; + cur_sp +=3D 8; + break; + case UWOP_ALLOC_LARGE: + if (PEX64_UNWCODE_INFO (p[1]) =3D=3D 0) + cur_sp +=3D + 8 * extract_unsigned_integer (p + 2, 2, byte_order); + else if (PEX64_UNWCODE_INFO (p[1]) =3D=3D 1) + cur_sp +=3D extract_unsigned_integer (p + 2, 4, byte_order); + else + return; + break; + case UWOP_ALLOC_SMALL: + cur_sp +=3D 8 + 8 * PEX64_UNWCODE_INFO (p[1]); + break; + case UWOP_SET_FPREG: + cur_sp =3D save_addr + - PEX64_UWI_FRAMEOFF (ex_ui.FrameRegisterOffset) * 16; + break; + case UWOP_SAVE_NONVOL: + reg =3D x64_w2gdb_regnum[PEX64_UNWCODE_INFO (p[1])]; + cache->prev_reg_addr[reg] =3D save_addr + + 8 * extract_unsigned_integer (p + 2, 2, byte_order); + break; + case UWOP_SAVE_NONVOL_FAR: + reg =3D x64_w2gdb_regnum[PEX64_UNWCODE_INFO (p[1])]; + cache->prev_reg_addr[reg] =3D save_addr + + 8 * extract_unsigned_integer (p + 2, 4, byte_order); + break; + case UWOP_SAVE_XMM128: + cache->prev_xmm_addr[PEX64_UNWCODE_INFO (p[1])] =3D + save_addr + + 8 * extract_unsigned_integer (p + 2, 2, byte_order); + break; + case UWOP_SAVE_XMM128_FAR: + cache->prev_xmm_addr[PEX64_UNWCODE_INFO (p[1])] =3D + save_addr + + 8 * extract_unsigned_integer (p + 2, 4, byte_order); + break; + case UWOP_PUSH_MACHFRAME: + if (PEX64_UNWCODE_INFO (p[1]) =3D=3D 0) + { + cache->prev_rip_addr =3D cur_sp + 0; + cache->prev_rsp_addr =3D cur_sp + 24; + cur_sp +=3D 40; + } + else if (PEX64_UNWCODE_INFO (p[1]) =3D=3D 1) + { + cache->prev_rip_addr =3D cur_sp + 8; + cache->prev_rsp_addr =3D cur_sp + 32; + cur_sp +=3D 48; + } + else + return; + break; + default: + return; + } + } + + /* Adjust with the length of the opcode. */ + switch (PEX64_UNWCODE_CODE (p[1])) + { + case UWOP_PUSH_NONVOL: + case UWOP_ALLOC_SMALL: + case UWOP_SET_FPREG: + case UWOP_PUSH_MACHFRAME: + break; + case UWOP_ALLOC_LARGE: + if (PEX64_UNWCODE_INFO (p[1]) =3D=3D 0) + p +=3D 2; + else if (PEX64_UNWCODE_INFO (p[1]) =3D=3D 1) + p +=3D 4; + else + return; + break; + case UWOP_SAVE_NONVOL: + case UWOP_SAVE_XMM128: + p +=3D 2; + break; + case UWOP_SAVE_NONVOL_FAR: + case UWOP_SAVE_XMM128_FAR: + p +=3D 4; + break; + default: + return; + } + } + if (PEX64_UWI_FLAGS (ex_ui.Version_Flags) !=3D UNW_FLAG_CHAININFO) + break; + else + { + /* Read the chained unwind info. */ + struct external_pex64_runtime_function d; + CORE_ADDR chain_vma; + + chain_vma =3D cache->image_base + unwind_info + + sizeof (ex_ui) + ((codes_count + 1) & ~1) * 2 + 8; + + if (target_read_memory (chain_vma, (char *) &d, sizeof (d)) !=3D 0) + return; + + cache->start_address =3D + extract_unsigned_integer (d.rva_BeginAddress, 4, byte_order); + unwind_info =3D + extract_unsigned_integer (d.rva_EndAddress, 4, byte_order); + } + + /* Allow the user to break this loop. */ + if (quit_flag) + return; + } + /* PC is saved by the call. */ + if (cache->prev_rip_addr =3D=3D 0) + cache->prev_rip_addr =3D cur_sp; + cache->prev_sp =3D cur_sp + 8; + + if (unwind_debug) + fprintf_unfiltered (gdb_stdlog, " prev_sp: %s, prev_pc @%s\n", + paddress (gdbarch, cache->prev_sp), + paddress (gdbarch, cache->prev_rip_addr)); +} + +static struct x64_frame_cache * +x64_frame_cache (struct frame_info *this_frame, void **this_cache) +{ + struct gdbarch *gdbarch =3D get_frame_arch (this_frame); + enum bfd_endian byte_order =3D gdbarch_byte_order (gdbarch); + struct x64_frame_cache *cache; + char buf[8]; + struct obj_section *sec; + pe_data_type *pe; + IMAGE_DATA_DIRECTORY *dir; + CORE_ADDR image_base; + CORE_ADDR pc; + struct objfile *objfile; + unsigned long lo, hi; + CORE_ADDR unwind_info; + + if (*this_cache) + return *this_cache; + + cache =3D FRAME_OBSTACK_ZALLOC (struct x64_frame_cache); + *this_cache =3D cache; + + /* Get current PC and SP. */ + pc =3D get_frame_pc (this_frame); + get_frame_register (this_frame, AMD64_RSP_REGNUM, buf); + cache->sp =3D extract_unsigned_integer (buf, 8, byte_order); + cache->pc =3D pc; + + /* Get the corresponding exception directory. */ + sec =3D find_pc_section (pc); + if (unwind_debug) + fprintf_unfiltered (gdb_stdlog, "x64_frame_cache: pc=3D%s, sp=3D%s, se= c=3D%p\n", + paddress (gdbarch, pc), + paddress (gdbarch, cache->sp), sec); + if (sec =3D=3D NULL) + return cache; + objfile =3D sec->objfile; + pe =3D pe_data (sec->objfile->obfd); + dir =3D &pe->pe_opthdr.DataDirectory[PE_EXCEPTION_TABLE]; + + image_base =3D pe->pe_opthdr.ImageBase + + ANOFFSET (objfile->section_offsets, SECT_OFF_TEXT (objfile)); + cache->image_base =3D image_base; + + if (unwind_debug) + fprintf_unfiltered (gdb_stdlog, + "x64_frame_cache: file:%s, image_base=3D%s\n", + objfile->name, paddress (gdbarch, image_base)); + + /* Find the entry. + Note: it might be a better idea to ask directly to the kernel. This + will handle dynamically added entries (for JIT engines). */ + lo =3D 0; + hi =3D dir->Size / sizeof (struct external_pex64_runtime_function); + unwind_info =3D 0; + while (lo <=3D hi) + { + unsigned long mid =3D lo + (hi - lo) / 2; + struct external_pex64_runtime_function d; + CORE_ADDR sa, ea; + + if (target_read_memory + (image_base + dir->VirtualAddress + mid * sizeof (d), + (char *) &d, sizeof (d)) !=3D 0) + return cache; + + sa =3D extract_unsigned_integer (d.rva_BeginAddress, 4, byte_order); + ea =3D extract_unsigned_integer (d.rva_EndAddress, 4, byte_order); + if (pc < image_base + sa) + hi =3D mid - 1; + else if (pc >=3D image_base + ea) + lo =3D mid + 1; + else if (pc >=3D image_base + sa && pc < image_base + ea) + { + /* Got it. */ + cache->start_address =3D sa; + unwind_info =3D + extract_unsigned_integer (d.rva_UnwindData, 4, byte_order); + break; + } + else + break; + } + + if (unwind_debug) + fprintf_unfiltered (gdb_stdlog, + "x64_frame_cache: image_base=3D%s, unwind_data=3D%s\n", + paddress (gdbarch, cache->image_base), + paddress (gdbarch, unwind_info)); + + if (unwind_info =3D=3D 0) + { + /* Assume a leaf function. */ + cache->prev_sp =3D cache->sp + 8; + cache->prev_rip_addr =3D cache->sp; + } + else + { + if (unwind_info & 1) + { + /* Unofficially documented unwind info redirection. */ + struct external_pex64_runtime_function d; + CORE_ADDR sa, ea; + + if (target_read_memory (image_base + (unwind_info & ~1), + (char *) &d, sizeof (d)) !=3D 0) + return cache; + + cache->start_address =3D + extract_unsigned_integer (d.rva_BeginAddress, 4, byte_order); + unwind_info =3D + extract_unsigned_integer (d.rva_EndAddress, 4, byte_order); + } + + /* Decode unwind insns to compute saved addresses. */ + x64_frame_decode_insns (this_frame, cache, unwind_info); + } + return cache; +} + +static struct value * +x64_frame_prev_register (struct frame_info *this_frame, + void **this_cache, int regnum) +{ + struct gdbarch *gdbarch =3D get_frame_arch (this_frame); + enum bfd_endian byte_order =3D gdbarch_byte_order (gdbarch); + struct x64_frame_cache *cache =3D x64_frame_cache (this_frame, this_cach= e); + struct value *val; + CORE_ADDR prev; + + if (unwind_debug) + fprintf_unfiltered (gdb_stdlog, "x64_frame_prev_register %s for sp=3D%= s\n", + gdbarch_register_name (gdbarch, regnum), + paddress (gdbarch, cache->prev_sp)); + + if (regnum >=3D AMD64_XMM0_REGNUM && regnum <=3D AMD64_XMM0_REGNUM + 15) + prev =3D cache->prev_xmm_addr[regnum - AMD64_XMM0_REGNUM]; + else if (regnum =3D=3D AMD64_RSP_REGNUM) + { + prev =3D cache->prev_rsp_addr; + if (prev =3D=3D 0) + return frame_unwind_got_constant (this_frame, regnum, cache->prev_sp); + } + else if (regnum >=3D AMD64_RAX_REGNUM && regnum <=3D AMD64_R15_REGNUM) + prev =3D cache->prev_reg_addr[regnum - AMD64_RAX_REGNUM]; + else if (regnum =3D=3D AMD64_RIP_REGNUM) + prev =3D cache->prev_rip_addr; + else + prev =3D 0; + + if (prev && unwind_debug) + fprintf_unfiltered (gdb_stdlog, " -> at %s\n", paddress (gdbarch, pre= v)); + + if (prev) + return frame_unwind_got_memory (this_frame, regnum, prev); + else + return frame_unwind_got_register (this_frame, regnum, regnum); +} + +static void +x64_frame_this_id (struct frame_info *this_frame, void **this_cache, + struct frame_id *this_id) +{ + struct gdbarch *gdbarch =3D get_frame_arch (this_frame); + struct x64_frame_cache *cache =3D x64_frame_cache (this_frame, this_cach= e); + + *this_id =3D frame_id_build (cache->prev_sp, + cache->image_base + cache->start_address); +} + +/* x64 SEH unwinder. */ + +static const struct frame_unwind x64_frame_unwind =3D +{ + NORMAL_FRAME, + default_frame_unwind_stop_reason, + &x64_frame_this_id, + &x64_frame_prev_register, + NULL, + default_frame_sniffer +}; =20 static void amd64_windows_init_abi (struct gdbarch_info info, struct gdbarch *gdbarch) @@ -174,6 +762,8 @@ amd64_windows_init_abi (struct gdbarch_info info, struc= t gdbarch *gdbarch) set_gdbarch_return_value (gdbarch, amd64_windows_return_value); set_gdbarch_skip_main_prologue (gdbarch, amd64_skip_main_prologue); =20 + frame_unwind_prepend_unwinder (gdbarch, &x64_frame_unwind); + set_solib_ops (gdbarch, &solib_target_so_ops); } =20