* [RFA] Add Windows x64 SEH unwinder
@ 2012-06-15 8:10 Tristan Gingold
2012-06-15 18:06 ` Joel Brobecker
2012-06-16 10:40 ` Mark Kettenis
0 siblings, 2 replies; 7+ messages in thread
From: Tristan Gingold @ 2012-06-15 8:10 UTC (permalink / raw)
To: gdb-patches@sourceware.org ml; +Cc: Kai Tietz
Hi,
the Windows x64 ABI specifies unwind data info in order to unwind frames for 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 that gdb is now able to unwind through code compiled with other compilers (and through system libraries).
I haven't run the gdb testsuite on Windows x64 (I don't know if this is doable), but I have manually tested it on a few executables, including gdb itself.
Comments are welcome.
Tristan.
2012-06-15 Tristan Gingold <gingold@adacore.com>
* 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"
/* The registers used to pass integer arguments during a function call. */
static int amd64_windows_dummy_call_integer_regs[] =
@@ -153,6 +159,588 @@ amd64_skip_main_prologue (struct gdbarch *gdbarch, CORE_ADDR pc)
return pc;
}
+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[] =
+{
+ 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 = cache->pc;
+ CORE_ADDR cur_sp = cache->sp;
+ struct gdbarch *gdbarch = get_frame_arch (this_frame);
+ enum bfd_endian byte_order = gdbarch_byte_order (gdbarch);
+
+ while (1)
+ {
+ gdb_byte op;
+ gdb_byte rex;
+
+ if (target_read_memory (pc, &op, 1) != 0)
+ return -1;
+
+ if (op == 0xc3)
+ {
+ /* Ret. */
+ cache->prev_rip_addr = cur_sp;
+ cache->prev_sp = cur_sp + 8;
+ return 1;
+ }
+ else if (op == 0xeb)
+ {
+ /* jmp rel8 */
+ gdb_byte rel8;
+
+ if (target_read_memory (pc + 1, &rel8, 1) != 0)
+ return -1;
+ pc = pc + 2 + (signed char)rel8;
+ }
+ else if (op == 0xec)
+ {
+ /* jmp rel32 */
+ gdb_byte rel32[4];
+
+ if (target_read_memory (pc + 1, rel32, 4) != 0)
+ return -1;
+ pc = pc + 5 + extract_signed_integer (rel32, 4, byte_order);
+ }
+ else if (op >= 0x58 && op <= 0x5f)
+ {
+ /* pop reg */
+ cache->prev_reg_addr[x64_w2gdb_regnum[op & 0x0f]] = cur_sp;
+ cur_sp += 8;
+ }
+ else if (op == 0xc2)
+ {
+ /* ret n */
+ gdb_byte imm16[2];
+
+ if (target_read_memory (pc + 1, imm16, 2) != 0)
+ return -1;
+ cache->prev_rip_addr = cur_sp;
+ cache->prev_sp = cur_sp
+ + extract_unsigned_integer (imm16, 4, byte_order);
+ return 1;
+ }
+ else if (op == 0xf3)
+ {
+ /* rep; ret */
+ gdb_byte op1;
+
+ if (target_read_memory (pc + 2, &op1, 1) != 0)
+ return -1;
+ if (op1 != 0xc3)
+ return 0;
+
+ cache->prev_rip_addr = cur_sp;
+ cache->prev_sp = 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 = op;
+ if (target_read_memory (pc + 1, &op, 1) != 0)
+ return -1;
+
+ if (op >= 0x58 && op <= 0x5f)
+ {
+ /* pop reg */
+ unsigned int reg;
+
+ reg = (op & 0x0f) | ((rex & 1) << 3);
+ cache->prev_reg_addr[x64_w2gdb_regnum[reg]] = cur_sp;
+ cur_sp += 8;
+ }
+ else if (op == 0xff)
+ {
+ /* rex jmp reg */
+ gdb_byte op1;
+ unsigned int reg;
+ gdb_byte buf[8];
+
+ if (target_read_memory (pc + 2, &op1, 1) != 0)
+ return -1;
+ if ((op1 & 0xf8) != 0xe0)
+ return 0;
+ reg = (op1 & 0x0f) | ((rex & 1) << 3);
+
+ get_frame_register (this_frame, x64_w2gdb_regnum[reg], buf);
+ pc = 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 = 0;
+ CORE_ADDR cur_sp = cache->sp;
+ struct gdbarch *gdbarch = get_frame_arch (this_frame);
+ enum bfd_endian byte_order = gdbarch_byte_order (gdbarch);
+ int j;
+
+ for (j = 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)) != 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) != 1)
+ return;
+
+ if (j == 0
+ && (cache->pc >=
+ 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) == 1)
+ return;
+
+ /* Not in an epilog. Clear possible side effects. */
+ memset (cache->prev_reg_addr, 0, sizeof (cache->prev_reg_addr));
+ }
+
+ codes_count = ex_ui.CountOfCodes;
+ frame_reg = PEX64_UWI_FRAMEREG (ex_ui.FrameRegisterOffset);
+
+ if (frame_reg != 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 = x64_w2gdb_regnum[frame_reg];
+
+ get_frame_register (this_frame, frreg, buf);
+ save_addr = extract_unsigned_integer (buf, 8, byte_order);
+
+ if (unwind_debug)
+ fprintf_unfiltered (gdb_stdlog, " frame_reg=%s, val=%s\n",
+ gdbarch_register_name (gdbarch, frreg),
+ paddress (gdbarch, save_addr));
+ }
+
+ /* Read opcodes. */
+ if (codes_count != 0
+ && target_read_memory (cache->image_base + unwind_info
+ + sizeof (ex_ui),
+ insns, codes_count * 2) != 0)
+ return;
+
+ end_insns = &insns[codes_count * 2];
+ for (p = insns; p < end_insns; p += 2)
+ {
+ int reg;
+
+ if (unwind_debug)
+ fprintf_unfiltered
+ (gdb_stdlog, " op #%u: off=0x%02x, insn=0x%02x\n",
+ (unsigned) (p - insns), p[0], p[1]);
+
+ /* Virtually execute the operation. */
+ if (cache->pc >= 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 == 0)
+ save_addr = cur_sp;
+
+ switch (PEX64_UNWCODE_CODE (p[1]))
+ {
+ case UWOP_PUSH_NONVOL:
+ /* Push pre-decrements RSP. */
+ reg = x64_w2gdb_regnum[PEX64_UNWCODE_INFO (p[1])];
+ cache->prev_reg_addr[reg] = cur_sp;
+ cur_sp += 8;
+ break;
+ case UWOP_ALLOC_LARGE:
+ if (PEX64_UNWCODE_INFO (p[1]) == 0)
+ cur_sp +=
+ 8 * extract_unsigned_integer (p + 2, 2, byte_order);
+ else if (PEX64_UNWCODE_INFO (p[1]) == 1)
+ cur_sp += extract_unsigned_integer (p + 2, 4, byte_order);
+ else
+ return;
+ break;
+ case UWOP_ALLOC_SMALL:
+ cur_sp += 8 + 8 * PEX64_UNWCODE_INFO (p[1]);
+ break;
+ case UWOP_SET_FPREG:
+ cur_sp = save_addr
+ - PEX64_UWI_FRAMEOFF (ex_ui.FrameRegisterOffset) * 16;
+ break;
+ case UWOP_SAVE_NONVOL:
+ reg = x64_w2gdb_regnum[PEX64_UNWCODE_INFO (p[1])];
+ cache->prev_reg_addr[reg] = save_addr
+ + 8 * extract_unsigned_integer (p + 2, 2, byte_order);
+ break;
+ case UWOP_SAVE_NONVOL_FAR:
+ reg = x64_w2gdb_regnum[PEX64_UNWCODE_INFO (p[1])];
+ cache->prev_reg_addr[reg] = 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])] =
+ 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])] =
+ save_addr
+ + 8 * extract_unsigned_integer (p + 2, 4, byte_order);
+ break;
+ case UWOP_PUSH_MACHFRAME:
+ if (PEX64_UNWCODE_INFO (p[1]) == 0)
+ {
+ cache->prev_rip_addr = cur_sp + 0;
+ cache->prev_rsp_addr = cur_sp + 24;
+ cur_sp += 40;
+ }
+ else if (PEX64_UNWCODE_INFO (p[1]) == 1)
+ {
+ cache->prev_rip_addr = cur_sp + 8;
+ cache->prev_rsp_addr = cur_sp + 32;
+ cur_sp += 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]) == 0)
+ p += 2;
+ else if (PEX64_UNWCODE_INFO (p[1]) == 1)
+ p += 4;
+ else
+ return;
+ break;
+ case UWOP_SAVE_NONVOL:
+ case UWOP_SAVE_XMM128:
+ p += 2;
+ break;
+ case UWOP_SAVE_NONVOL_FAR:
+ case UWOP_SAVE_XMM128_FAR:
+ p += 4;
+ break;
+ default:
+ return;
+ }
+ }
+ if (PEX64_UWI_FLAGS (ex_ui.Version_Flags) != UNW_FLAG_CHAININFO)
+ break;
+ else
+ {
+ /* Read the chained unwind info. */
+ struct external_pex64_runtime_function d;
+ CORE_ADDR chain_vma;
+
+ chain_vma = cache->image_base + unwind_info
+ + sizeof (ex_ui) + ((codes_count + 1) & ~1) * 2 + 8;
+
+ if (target_read_memory (chain_vma, (char *) &d, sizeof (d)) != 0)
+ return;
+
+ cache->start_address =
+ extract_unsigned_integer (d.rva_BeginAddress, 4, byte_order);
+ unwind_info =
+ 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 == 0)
+ cache->prev_rip_addr = cur_sp;
+ cache->prev_sp = 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 = get_frame_arch (this_frame);
+ enum bfd_endian byte_order = 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 = FRAME_OBSTACK_ZALLOC (struct x64_frame_cache);
+ *this_cache = cache;
+
+ /* Get current PC and SP. */
+ pc = get_frame_pc (this_frame);
+ get_frame_register (this_frame, AMD64_RSP_REGNUM, buf);
+ cache->sp = extract_unsigned_integer (buf, 8, byte_order);
+ cache->pc = pc;
+
+ /* Get the corresponding exception directory. */
+ sec = find_pc_section (pc);
+ if (unwind_debug)
+ fprintf_unfiltered (gdb_stdlog, "x64_frame_cache: pc=%s, sp=%s, sec=%p\n",
+ paddress (gdbarch, pc),
+ paddress (gdbarch, cache->sp), sec);
+ if (sec == NULL)
+ return cache;
+ objfile = sec->objfile;
+ pe = pe_data (sec->objfile->obfd);
+ dir = &pe->pe_opthdr.DataDirectory[PE_EXCEPTION_TABLE];
+
+ image_base = pe->pe_opthdr.ImageBase
+ + ANOFFSET (objfile->section_offsets, SECT_OFF_TEXT (objfile));
+ cache->image_base = image_base;
+
+ if (unwind_debug)
+ fprintf_unfiltered (gdb_stdlog,
+ "x64_frame_cache: file:%s, image_base=%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 = 0;
+ hi = dir->Size / sizeof (struct external_pex64_runtime_function);
+ unwind_info = 0;
+ while (lo <= hi)
+ {
+ unsigned long mid = 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)) != 0)
+ return cache;
+
+ sa = extract_unsigned_integer (d.rva_BeginAddress, 4, byte_order);
+ ea = extract_unsigned_integer (d.rva_EndAddress, 4, byte_order);
+ if (pc < image_base + sa)
+ hi = mid - 1;
+ else if (pc >= image_base + ea)
+ lo = mid + 1;
+ else if (pc >= image_base + sa && pc < image_base + ea)
+ {
+ /* Got it. */
+ cache->start_address = sa;
+ unwind_info =
+ extract_unsigned_integer (d.rva_UnwindData, 4, byte_order);
+ break;
+ }
+ else
+ break;
+ }
+
+ if (unwind_debug)
+ fprintf_unfiltered (gdb_stdlog,
+ "x64_frame_cache: image_base=%s, unwind_data=%s\n",
+ paddress (gdbarch, cache->image_base),
+ paddress (gdbarch, unwind_info));
+
+ if (unwind_info == 0)
+ {
+ /* Assume a leaf function. */
+ cache->prev_sp = cache->sp + 8;
+ cache->prev_rip_addr = 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)) != 0)
+ return cache;
+
+ cache->start_address =
+ extract_unsigned_integer (d.rva_BeginAddress, 4, byte_order);
+ unwind_info =
+ 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 = get_frame_arch (this_frame);
+ enum bfd_endian byte_order = gdbarch_byte_order (gdbarch);
+ struct x64_frame_cache *cache = x64_frame_cache (this_frame, this_cache);
+ struct value *val;
+ CORE_ADDR prev;
+
+ if (unwind_debug)
+ fprintf_unfiltered (gdb_stdlog, "x64_frame_prev_register %s for sp=%s\n",
+ gdbarch_register_name (gdbarch, regnum),
+ paddress (gdbarch, cache->prev_sp));
+
+ if (regnum >= AMD64_XMM0_REGNUM && regnum <= AMD64_XMM0_REGNUM + 15)
+ prev = cache->prev_xmm_addr[regnum - AMD64_XMM0_REGNUM];
+ else if (regnum == AMD64_RSP_REGNUM)
+ {
+ prev = cache->prev_rsp_addr;
+ if (prev == 0)
+ return frame_unwind_got_constant (this_frame, regnum, cache->prev_sp);
+ }
+ else if (regnum >= AMD64_RAX_REGNUM && regnum <= AMD64_R15_REGNUM)
+ prev = cache->prev_reg_addr[regnum - AMD64_RAX_REGNUM];
+ else if (regnum == AMD64_RIP_REGNUM)
+ prev = cache->prev_rip_addr;
+ else
+ prev = 0;
+
+ if (prev && unwind_debug)
+ fprintf_unfiltered (gdb_stdlog, " -> at %s\n", paddress (gdbarch, prev));
+
+ 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 = get_frame_arch (this_frame);
+ struct x64_frame_cache *cache = x64_frame_cache (this_frame, this_cache);
+
+ *this_id = frame_id_build (cache->prev_sp,
+ cache->image_base + cache->start_address);
+}
+
+/* x64 SEH unwinder. */
+
+static const struct frame_unwind x64_frame_unwind =
+{
+ NORMAL_FRAME,
+ default_frame_unwind_stop_reason,
+ &x64_frame_this_id,
+ &x64_frame_prev_register,
+ NULL,
+ default_frame_sniffer
+};
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, struct gdbarch *gdbarch)
set_gdbarch_return_value (gdbarch, amd64_windows_return_value);
set_gdbarch_skip_main_prologue (gdbarch, amd64_skip_main_prologue);
+ frame_unwind_prepend_unwinder (gdbarch, &x64_frame_unwind);
+
set_solib_ops (gdbarch, &solib_target_so_ops);
}
^ permalink raw reply [flat|nested] 7+ messages in thread* Re: [RFA] Add Windows x64 SEH unwinder
2012-06-15 8:10 [RFA] Add Windows x64 SEH unwinder Tristan Gingold
@ 2012-06-15 18:06 ` Joel Brobecker
2012-06-18 9:23 ` Tristan Gingold
2012-06-16 10:40 ` Mark Kettenis
1 sibling, 1 reply; 7+ messages in thread
From: Joel Brobecker @ 2012-06-15 18:06 UTC (permalink / raw)
To: Tristan Gingold; +Cc: gdb-patches@sourceware.org ml, Kai Tietz
> This patch adds an unwinder that reads these data. The main advantage
> is that gdb is now able to unwind through code compiled with other
> compilers (and through system libraries).
I see you beat me to this task, and you didn't even tell me you started
:-). Thanks for doing it!
> I haven't run the gdb testsuite on Windows x64 (I don't know if this
> is doable), but I have manually tested it on a few executables,
> including gdb itself.
It would be good if you could test those changes against AdaCore's
testsuite, which we know runs well on x64. It's not as complete as
the official testsuite, but will already test quite a bit, and will
definitely be better than nothing.
Because you prepend the unwinder, I think that this unwinder will
take precedence over the DWARF unwinder (right?), and so it's
important we get a minimum amount of confidence before we apply it
(hence the testing suggestion above). We are also trying to get ready
to branch 7.5, and I don't think I would feel all that comfortable
applying this now.
> 2012-06-15 Tristan Gingold <gingold@adacore.com>
>
> * 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.
Some comments below. Apologies in advance for being anal on certain
things...
> +struct x64_frame_cache
> +{
> + CORE_ADDR image_base; /* ImageBase for the module. */
> + CORE_ADDR start_address; /* Function start rva. */
The more usual style for the comments is to place them on their own
line, just before the field itself. I know the style you chose is
acceptable because you were able to make them fit within the line.
But I'd rather have them follow my suggested style, because we can
then extend some of them to multi-line comments if necessary.
> +/* Try to recognize and decode an epilogue sequence. */
> +
> +static int
> +x64_frame_decode_epilogue (struct frame_info *this_frame,
> + struct x64_frame_cache *cache)
I am amazed that one would still need to do manual epilogue
detection and associated by-hand instruction scanning when
using any unwind info. But I assume the info is not complete
enough and there is no other way?
> +{
> + /* 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]
(can we align the text in square brackets?)
> + We don't care about the instruction that deallocate the frame:
deallocates
> + if it hasn't been executed, we can safely decode the insns,
> + if it has been executed, the following epilog decoding will
> + work.
> + */
Can you join the comment-closing marker with the last line of the
comment?
> + pc = pc + 2 + (signed char)rel8;
space after closing parens, even for casts...
> + /* Allow the user to break this loop. */
> + if (quit_flag)
> + return 0;
Why not use the QUIT macro? I assume that you want to avoid
the call to the "quit" function, and return 0 instead. But
that seems very odd to me, and maybe even wrong, because you'll
be continuing the unwind even though the user pressed ctrl-c,
with unpredictable results.
> + for (j = 0; ; j++)
> + {
> + struct external_pex64_unwind_info ex_ui;
> + gdb_byte insns[2 * 256];
I am a little concerned about this magic number. Can you explain
how you came to it? The multiplication seems to suggest that there
is a logic behind it.
> + /* 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. */
^^^^^^^
(guess: after?)
> + /* Allow the user to break this loop. */
> + if (quit_flag)
> + return;
Same comment as above.
> +static struct x64_frame_cache *
> +x64_frame_cache (struct frame_info *this_frame, void **this_cache)
Function documentation missing. This
> + /* 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). */
I am fine with the current approach, but this makes me wondering
how we would do that, and why we are not doing it? Asking the kernel
from a tdep file means that we'd have to define another xfer object
kind, so that's the first obstacle...
> + if (unwind_info & 1)
?
> + if (target_read_memory (image_base + (unwind_info & ~1),
> + (char *) &d, sizeof (d)) != 0)
^^^^^^^^ (gdb_byte *)
I am realizing I might have missed other cases of casting to
"char *".
> +static struct value *
> +x64_frame_prev_register (struct frame_info *this_frame,
> + void **this_cache, int regnum)
Missing function comment.
> +static void
> +x64_frame_this_id (struct frame_info *this_frame, void **this_cache,
> + struct frame_id *this_id)
Same...
All in all, I'd say, pretty nice!
--
Joel
^ permalink raw reply [flat|nested] 7+ messages in thread* Re: [RFA] Add Windows x64 SEH unwinder
2012-06-15 18:06 ` Joel Brobecker
@ 2012-06-18 9:23 ` Tristan Gingold
0 siblings, 0 replies; 7+ messages in thread
From: Tristan Gingold @ 2012-06-18 9:23 UTC (permalink / raw)
To: Joel Brobecker; +Cc: gdb-patches@sourceware.org ml, Kai Tietz
On Jun 15, 2012, at 8:06 PM, Joel Brobecker wrote:
>> This patch adds an unwinder that reads these data. The main advantage
>> is that gdb is now able to unwind through code compiled with other
>> compilers (and through system libraries).
>
> I see you beat me to this task, and you didn't even tell me you started
> :-). Thanks for doing it!
>
>> I haven't run the gdb testsuite on Windows x64 (I don't know if this
>> is doable), but I have manually tested it on a few executables,
>> including gdb itself.
>
> It would be good if you could test those changes against AdaCore's
> testsuite, which we know runs well on x64. It's not as complete as
> the official testsuite, but will already test quite a bit, and will
> definitely be better than nothing.
Ok, will do it.
> Because you prepend the unwinder, I think that this unwinder will
> take precedence over the DWARF unwinder (right?), and so it's
> important we get a minimum amount of confidence before we apply it
> (hence the testing suggestion above). We are also trying to get ready
> to branch 7.5, and I don't think I would feel all that comfortable
> applying this now.
[...]
>> +/* Try to recognize and decode an epilogue sequence. */
>> +
>> +static int
>> +x64_frame_decode_epilogue (struct frame_info *this_frame,
>> + struct x64_frame_cache *cache)
>
> I am amazed that one would still need to do manual epilogue
> detection and associated by-hand instruction scanning when
> using any unwind info. But I assume the info is not complete
> enough and there is no other way?
No, this is simply part of the specs; but there are only a few patterns to consider.
[...]
>> + /* Allow the user to break this loop. */
>> + if (quit_flag)
>> + return 0;
>
> Why not use the QUIT macro? I assume that you want to avoid
> the call to the "quit" function, and return 0 instead. But
> that seems very odd to me, and maybe even wrong, because you'll
> be continuing the unwind even though the user pressed ctrl-c,
> with unpredictable results.
Ok.
[...]
>> + for (j = 0; ; j++)
>> + {
>> + struct external_pex64_unwind_info ex_ui;
>> + gdb_byte insns[2 * 256];
>
> I am a little concerned about this magic number. Can you explain
> how you came to it? The multiplication seems to suggest that there
> is a logic behind it.
I added a comment about that.
[...]
>> + /* 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). */
>
> I am fine with the current approach, but this makes me wondering
> how we would do that, and why we are not doing it? Asking the kernel
> from a tdep file means that we'd have to define another xfer object
> kind, so that's the first obstacle...
Yes, yet another xfer object will do it...
>> + if (unwind_info & 1)
>
> ?
I have adjust the comment about this (may not be in the second version just posted).
>> + if (target_read_memory (image_base + (unwind_info & ~1),
>> + (char *) &d, sizeof (d)) != 0)
> ^^^^^^^^ (gdb_byte *)
>
> I am realizing I might have missed other cases of casting to
> "char *".
I fixed all of them (at least in my current tree).
Thank you for your extensive review,
Tristan
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [RFA] Add Windows x64 SEH unwinder
2012-06-15 8:10 [RFA] Add Windows x64 SEH unwinder Tristan Gingold
2012-06-15 18:06 ` Joel Brobecker
@ 2012-06-16 10:40 ` Mark Kettenis
2012-06-18 9:18 ` Tristan Gingold
1 sibling, 1 reply; 7+ messages in thread
From: Mark Kettenis @ 2012-06-16 10:40 UTC (permalink / raw)
To: gingold; +Cc: gdb-patches, ktietz70
> From: Tristan Gingold <gingold@adacore.com>
> Date: Fri, 15 Jun 2012 10:09:56 +0200
>
> Hi,
>
> the Windows x64 ABI specifies unwind data info in order to unwind
> frames for 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 that gdb is now able to unwind through code compiled
> with other compilers (and through system libraries).
>
> I haven't run the gdb testsuite on Windows x64 (I don't know if this
> is doable), but I have manually tested it on a few executables,
> including gdb itself.
>
> Comments are welcome.
The amd64 codebase consistently uses the amd64_-prefix for structures,
variables and functions. If this is effectively *the* unwinder for
64-bit windows, I'd recommend "amd64_windows_". If you expect more
Windows-specfic unwinders (or think amd64_windows_ is too long)
"amd64_seh_" might be a good alternative.
> 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"
>
> /* The registers used to pass integer arguments during a function call. */
> static int amd64_windows_dummy_call_integer_regs[] =
> @@ -153,6 +159,588 @@ amd64_skip_main_prologue (struct gdbarch *gdbarch, CORE_ADDR pc)
> return pc;
> }
>
> +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[] =
> +{
> + 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 = cache->pc;
> + CORE_ADDR cur_sp = cache->sp;
> + struct gdbarch *gdbarch = get_frame_arch (this_frame);
> + enum bfd_endian byte_order = gdbarch_byte_order (gdbarch);
> +
> + while (1)
> + {
> + gdb_byte op;
> + gdb_byte rex;
> +
> + if (target_read_memory (pc, &op, 1) != 0)
> + return -1;
> +
> + if (op == 0xc3)
> + {
> + /* Ret. */
> + cache->prev_rip_addr = cur_sp;
> + cache->prev_sp = cur_sp + 8;
> + return 1;
> + }
> + else if (op == 0xeb)
> + {
> + /* jmp rel8 */
> + gdb_byte rel8;
> +
> + if (target_read_memory (pc + 1, &rel8, 1) != 0)
> + return -1;
> + pc = pc + 2 + (signed char)rel8;
> + }
> + else if (op == 0xec)
> + {
> + /* jmp rel32 */
> + gdb_byte rel32[4];
> +
> + if (target_read_memory (pc + 1, rel32, 4) != 0)
> + return -1;
> + pc = pc + 5 + extract_signed_integer (rel32, 4, byte_order);
> + }
> + else if (op >= 0x58 && op <= 0x5f)
> + {
> + /* pop reg */
> + cache->prev_reg_addr[x64_w2gdb_regnum[op & 0x0f]] = cur_sp;
> + cur_sp += 8;
> + }
> + else if (op == 0xc2)
> + {
> + /* ret n */
> + gdb_byte imm16[2];
> +
> + if (target_read_memory (pc + 1, imm16, 2) != 0)
> + return -1;
> + cache->prev_rip_addr = cur_sp;
> + cache->prev_sp = cur_sp
> + + extract_unsigned_integer (imm16, 4, byte_order);
> + return 1;
> + }
> + else if (op == 0xf3)
> + {
> + /* rep; ret */
> + gdb_byte op1;
> +
> + if (target_read_memory (pc + 2, &op1, 1) != 0)
> + return -1;
> + if (op1 != 0xc3)
> + return 0;
> +
> + cache->prev_rip_addr = cur_sp;
> + cache->prev_sp = 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 = op;
> + if (target_read_memory (pc + 1, &op, 1) != 0)
> + return -1;
> +
> + if (op >= 0x58 && op <= 0x5f)
> + {
> + /* pop reg */
> + unsigned int reg;
> +
> + reg = (op & 0x0f) | ((rex & 1) << 3);
> + cache->prev_reg_addr[x64_w2gdb_regnum[reg]] = cur_sp;
> + cur_sp += 8;
> + }
> + else if (op == 0xff)
> + {
> + /* rex jmp reg */
> + gdb_byte op1;
> + unsigned int reg;
> + gdb_byte buf[8];
> +
> + if (target_read_memory (pc + 2, &op1, 1) != 0)
> + return -1;
> + if ((op1 & 0xf8) != 0xe0)
> + return 0;
> + reg = (op1 & 0x0f) | ((rex & 1) << 3);
> +
> + get_frame_register (this_frame, x64_w2gdb_regnum[reg], buf);
> + pc = 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 = 0;
> + CORE_ADDR cur_sp = cache->sp;
> + struct gdbarch *gdbarch = get_frame_arch (this_frame);
> + enum bfd_endian byte_order = gdbarch_byte_order (gdbarch);
> + int j;
> +
> + for (j = 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)) != 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) != 1)
> + return;
> +
> + if (j == 0
> + && (cache->pc >=
> + 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) == 1)
> + return;
> +
> + /* Not in an epilog. Clear possible side effects. */
> + memset (cache->prev_reg_addr, 0, sizeof (cache->prev_reg_addr));
> + }
> +
> + codes_count = ex_ui.CountOfCodes;
> + frame_reg = PEX64_UWI_FRAMEREG (ex_ui.FrameRegisterOffset);
> +
> + if (frame_reg != 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 = x64_w2gdb_regnum[frame_reg];
> +
> + get_frame_register (this_frame, frreg, buf);
> + save_addr = extract_unsigned_integer (buf, 8, byte_order);
> +
> + if (unwind_debug)
> + fprintf_unfiltered (gdb_stdlog, " frame_reg=%s, val=%s\n",
> + gdbarch_register_name (gdbarch, frreg),
> + paddress (gdbarch, save_addr));
> + }
> +
> + /* Read opcodes. */
> + if (codes_count != 0
> + && target_read_memory (cache->image_base + unwind_info
> + + sizeof (ex_ui),
> + insns, codes_count * 2) != 0)
> + return;
> +
> + end_insns = &insns[codes_count * 2];
> + for (p = insns; p < end_insns; p += 2)
> + {
> + int reg;
> +
> + if (unwind_debug)
> + fprintf_unfiltered
> + (gdb_stdlog, " op #%u: off=0x%02x, insn=0x%02x\n",
> + (unsigned) (p - insns), p[0], p[1]);
> +
> + /* Virtually execute the operation. */
> + if (cache->pc >= 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 == 0)
> + save_addr = cur_sp;
> +
> + switch (PEX64_UNWCODE_CODE (p[1]))
> + {
> + case UWOP_PUSH_NONVOL:
> + /* Push pre-decrements RSP. */
> + reg = x64_w2gdb_regnum[PEX64_UNWCODE_INFO (p[1])];
> + cache->prev_reg_addr[reg] = cur_sp;
> + cur_sp += 8;
> + break;
> + case UWOP_ALLOC_LARGE:
> + if (PEX64_UNWCODE_INFO (p[1]) == 0)
> + cur_sp +=
> + 8 * extract_unsigned_integer (p + 2, 2, byte_order);
> + else if (PEX64_UNWCODE_INFO (p[1]) == 1)
> + cur_sp += extract_unsigned_integer (p + 2, 4, byte_order);
> + else
> + return;
> + break;
> + case UWOP_ALLOC_SMALL:
> + cur_sp += 8 + 8 * PEX64_UNWCODE_INFO (p[1]);
> + break;
> + case UWOP_SET_FPREG:
> + cur_sp = save_addr
> + - PEX64_UWI_FRAMEOFF (ex_ui.FrameRegisterOffset) * 16;
> + break;
> + case UWOP_SAVE_NONVOL:
> + reg = x64_w2gdb_regnum[PEX64_UNWCODE_INFO (p[1])];
> + cache->prev_reg_addr[reg] = save_addr
> + + 8 * extract_unsigned_integer (p + 2, 2, byte_order);
> + break;
> + case UWOP_SAVE_NONVOL_FAR:
> + reg = x64_w2gdb_regnum[PEX64_UNWCODE_INFO (p[1])];
> + cache->prev_reg_addr[reg] = 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])] =
> + 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])] =
> + save_addr
> + + 8 * extract_unsigned_integer (p + 2, 4, byte_order);
> + break;
> + case UWOP_PUSH_MACHFRAME:
> + if (PEX64_UNWCODE_INFO (p[1]) == 0)
> + {
> + cache->prev_rip_addr = cur_sp + 0;
> + cache->prev_rsp_addr = cur_sp + 24;
> + cur_sp += 40;
> + }
> + else if (PEX64_UNWCODE_INFO (p[1]) == 1)
> + {
> + cache->prev_rip_addr = cur_sp + 8;
> + cache->prev_rsp_addr = cur_sp + 32;
> + cur_sp += 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]) == 0)
> + p += 2;
> + else if (PEX64_UNWCODE_INFO (p[1]) == 1)
> + p += 4;
> + else
> + return;
> + break;
> + case UWOP_SAVE_NONVOL:
> + case UWOP_SAVE_XMM128:
> + p += 2;
> + break;
> + case UWOP_SAVE_NONVOL_FAR:
> + case UWOP_SAVE_XMM128_FAR:
> + p += 4;
> + break;
> + default:
> + return;
> + }
> + }
> + if (PEX64_UWI_FLAGS (ex_ui.Version_Flags) != UNW_FLAG_CHAININFO)
> + break;
> + else
> + {
> + /* Read the chained unwind info. */
> + struct external_pex64_runtime_function d;
> + CORE_ADDR chain_vma;
> +
> + chain_vma = cache->image_base + unwind_info
> + + sizeof (ex_ui) + ((codes_count + 1) & ~1) * 2 + 8;
> +
> + if (target_read_memory (chain_vma, (char *) &d, sizeof (d)) != 0)
> + return;
> +
> + cache->start_address =
> + extract_unsigned_integer (d.rva_BeginAddress, 4, byte_order);
> + unwind_info =
> + 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 == 0)
> + cache->prev_rip_addr = cur_sp;
> + cache->prev_sp = 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 = get_frame_arch (this_frame);
> + enum bfd_endian byte_order = 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 = FRAME_OBSTACK_ZALLOC (struct x64_frame_cache);
> + *this_cache = cache;
> +
> + /* Get current PC and SP. */
> + pc = get_frame_pc (this_frame);
> + get_frame_register (this_frame, AMD64_RSP_REGNUM, buf);
> + cache->sp = extract_unsigned_integer (buf, 8, byte_order);
> + cache->pc = pc;
> +
> + /* Get the corresponding exception directory. */
> + sec = find_pc_section (pc);
> + if (unwind_debug)
> + fprintf_unfiltered (gdb_stdlog, "x64_frame_cache: pc=%s, sp=%s, sec=%p\n",
> + paddress (gdbarch, pc),
> + paddress (gdbarch, cache->sp), sec);
> + if (sec == NULL)
> + return cache;
> + objfile = sec->objfile;
> + pe = pe_data (sec->objfile->obfd);
> + dir = &pe->pe_opthdr.DataDirectory[PE_EXCEPTION_TABLE];
> +
> + image_base = pe->pe_opthdr.ImageBase
> + + ANOFFSET (objfile->section_offsets, SECT_OFF_TEXT (objfile));
> + cache->image_base = image_base;
> +
> + if (unwind_debug)
> + fprintf_unfiltered (gdb_stdlog,
> + "x64_frame_cache: file:%s, image_base=%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 = 0;
> + hi = dir->Size / sizeof (struct external_pex64_runtime_function);
> + unwind_info = 0;
> + while (lo <= hi)
> + {
> + unsigned long mid = 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)) != 0)
> + return cache;
> +
> + sa = extract_unsigned_integer (d.rva_BeginAddress, 4, byte_order);
> + ea = extract_unsigned_integer (d.rva_EndAddress, 4, byte_order);
> + if (pc < image_base + sa)
> + hi = mid - 1;
> + else if (pc >= image_base + ea)
> + lo = mid + 1;
> + else if (pc >= image_base + sa && pc < image_base + ea)
> + {
> + /* Got it. */
> + cache->start_address = sa;
> + unwind_info =
> + extract_unsigned_integer (d.rva_UnwindData, 4, byte_order);
> + break;
> + }
> + else
> + break;
> + }
> +
> + if (unwind_debug)
> + fprintf_unfiltered (gdb_stdlog,
> + "x64_frame_cache: image_base=%s, unwind_data=%s\n",
> + paddress (gdbarch, cache->image_base),
> + paddress (gdbarch, unwind_info));
> +
> + if (unwind_info == 0)
> + {
> + /* Assume a leaf function. */
> + cache->prev_sp = cache->sp + 8;
> + cache->prev_rip_addr = 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)) != 0)
> + return cache;
> +
> + cache->start_address =
> + extract_unsigned_integer (d.rva_BeginAddress, 4, byte_order);
> + unwind_info =
> + 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 = get_frame_arch (this_frame);
> + enum bfd_endian byte_order = gdbarch_byte_order (gdbarch);
> + struct x64_frame_cache *cache = x64_frame_cache (this_frame, this_cache);
> + struct value *val;
> + CORE_ADDR prev;
> +
> + if (unwind_debug)
> + fprintf_unfiltered (gdb_stdlog, "x64_frame_prev_register %s for sp=%s\n",
> + gdbarch_register_name (gdbarch, regnum),
> + paddress (gdbarch, cache->prev_sp));
> +
> + if (regnum >= AMD64_XMM0_REGNUM && regnum <= AMD64_XMM0_REGNUM + 15)
> + prev = cache->prev_xmm_addr[regnum - AMD64_XMM0_REGNUM];
> + else if (regnum == AMD64_RSP_REGNUM)
> + {
> + prev = cache->prev_rsp_addr;
> + if (prev == 0)
> + return frame_unwind_got_constant (this_frame, regnum, cache->prev_sp);
> + }
> + else if (regnum >= AMD64_RAX_REGNUM && regnum <= AMD64_R15_REGNUM)
> + prev = cache->prev_reg_addr[regnum - AMD64_RAX_REGNUM];
> + else if (regnum == AMD64_RIP_REGNUM)
> + prev = cache->prev_rip_addr;
> + else
> + prev = 0;
> +
> + if (prev && unwind_debug)
> + fprintf_unfiltered (gdb_stdlog, " -> at %s\n", paddress (gdbarch, prev));
> +
> + 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 = get_frame_arch (this_frame);
> + struct x64_frame_cache *cache = x64_frame_cache (this_frame, this_cache);
> +
> + *this_id = frame_id_build (cache->prev_sp,
> + cache->image_base + cache->start_address);
> +}
> +
> +/* x64 SEH unwinder. */
> +
> +static const struct frame_unwind x64_frame_unwind =
> +{
> + NORMAL_FRAME,
> + default_frame_unwind_stop_reason,
> + &x64_frame_this_id,
> + &x64_frame_prev_register,
> + NULL,
> + default_frame_sniffer
> +};
>
> 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, struct gdbarch *gdbarch)
> set_gdbarch_return_value (gdbarch, amd64_windows_return_value);
> set_gdbarch_skip_main_prologue (gdbarch, amd64_skip_main_prologue);
>
> + frame_unwind_prepend_unwinder (gdbarch, &x64_frame_unwind);
> +
> set_solib_ops (gdbarch, &solib_target_so_ops);
> }
>
>
>
>
^ permalink raw reply [flat|nested] 7+ messages in thread* Re: [RFA] Add Windows x64 SEH unwinder
2012-06-16 10:40 ` Mark Kettenis
@ 2012-06-18 9:18 ` Tristan Gingold
2012-06-18 16:12 ` Eli Zaretskii
2012-06-20 14:48 ` Joel Brobecker
0 siblings, 2 replies; 7+ messages in thread
From: Tristan Gingold @ 2012-06-18 9:18 UTC (permalink / raw)
To: Joel Brobecker; +Cc: gdb-patches@sourceware.org ml, Kai Tietz, Mark Kettenis
On Jun 16, 2012, at 12:39 PM, Mark Kettenis wrote:
>> From: Tristan Gingold <gingold@adacore.com>
>> Date: Fri, 15 Jun 2012 10:09:56 +0200
>>
>> Hi,
>>
>> the Windows x64 ABI specifies unwind data info in order to unwind
>> frames for 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 that gdb is now able to unwind through code compiled
>> with other compilers (and through system libraries).
>>
>> I haven't run the gdb testsuite on Windows x64 (I don't know if this
>> is doable), but I have manually tested it on a few executables,
>> including gdb itself.
>>
>> Comments are welcome.
>
> The amd64 codebase consistently uses the amd64_-prefix for structures,
> variables and functions. If this is effectively *the* unwinder for
> 64-bit windows, I'd recommend "amd64_windows_". If you expect more
> Windows-specfic unwinders (or think amd64_windows_ is too long)
> "amd64_seh_" might be a good alternative.
Thanks for the comments. Here is the new version.
Tristan.
2012-06-15 Tristan Gingold <gingold@adacore.com>
* 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.
* NEWS: Mention it.
diff --git a/gdb/NEWS b/gdb/NEWS
index 2384489..34903a2 100644
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -117,6 +117,8 @@
* GDB can now support 'breakpoint always-inserted mode' in 'record'
target.
+* GDB can now use Windows x64 unwinding data.
+
* MI changes
** New command -info-os is the MI equivalent of "info os".
diff --git a/gdb/amd64-windows-tdep.c b/gdb/amd64-windows-tdep.c
index 4a40f47..a16cf42 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"
/* The registers used to pass integer arguments during a function call. */
static int amd64_windows_dummy_call_integer_regs[] =
@@ -153,6 +159,620 @@ amd64_skip_main_prologue (struct gdbarch *gdbarch, CORE_ADDR pc)
return pc;
}
+struct amd64_windows_frame_cache
+{
+ /* ImageBase for the module. */
+ CORE_ADDR image_base;
+
+ /* Function start rva. */
+ CORE_ADDR start_address;
+
+ /* Next instruction to be executed. */
+ CORE_ADDR pc;
+
+ /* Current sp. */
+ CORE_ADDR sp;
+
+ /* Address of saved integer and xmm registers. */
+ CORE_ADDR prev_reg_addr[16];
+ CORE_ADDR prev_xmm_addr[16];
+
+ /* These two next fields are set only for machine info frames. */
+
+ /* Likewise for RIP. */
+ CORE_ADDR prev_rip_addr;
+
+ /* Likewise for RSP. */
+ CORE_ADDR prev_rsp_addr;
+
+ /* Address of the previous frame. */
+ CORE_ADDR prev_sp;
+};
+
+/* Convert a Windows register number to gdb. */
+static const enum amd64_regnum amd64_windows_w2gdb_regnum[] =
+{
+ 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
+amd64_windows_frame_decode_epilogue (struct frame_info *this_frame,
+ struct amd64_windows_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 = cache->pc;
+ CORE_ADDR cur_sp = cache->sp;
+ struct gdbarch *gdbarch = get_frame_arch (this_frame);
+ enum bfd_endian byte_order = gdbarch_byte_order (gdbarch);
+
+ while (1)
+ {
+ gdb_byte op;
+ gdb_byte rex;
+
+ if (target_read_memory (pc, &op, 1) != 0)
+ return -1;
+
+ if (op == 0xc3)
+ {
+ /* Ret. */
+ cache->prev_rip_addr = cur_sp;
+ cache->prev_sp = cur_sp + 8;
+ return 1;
+ }
+ else if (op == 0xeb)
+ {
+ /* jmp rel8 */
+ gdb_byte rel8;
+
+ if (target_read_memory (pc + 1, &rel8, 1) != 0)
+ return -1;
+ pc = pc + 2 + (signed char)rel8;
+ }
+ else if (op == 0xec)
+ {
+ /* jmp rel32 */
+ gdb_byte rel32[4];
+
+ if (target_read_memory (pc + 1, rel32, 4) != 0)
+ return -1;
+ pc = pc + 5 + extract_signed_integer (rel32, 4, byte_order);
+ }
+ else if (op >= 0x58 && op <= 0x5f)
+ {
+ /* pop reg */
+ cache->prev_reg_addr[amd64_windows_w2gdb_regnum[op & 0x0f]] = cur_sp;
+ cur_sp += 8;
+ }
+ else if (op == 0xc2)
+ {
+ /* ret n */
+ gdb_byte imm16[2];
+
+ if (target_read_memory (pc + 1, imm16, 2) != 0)
+ return -1;
+ cache->prev_rip_addr = cur_sp;
+ cache->prev_sp = cur_sp
+ + extract_unsigned_integer (imm16, 4, byte_order);
+ return 1;
+ }
+ else if (op == 0xf3)
+ {
+ /* rep; ret */
+ gdb_byte op1;
+
+ if (target_read_memory (pc + 2, &op1, 1) != 0)
+ return -1;
+ if (op1 != 0xc3)
+ return 0;
+
+ cache->prev_rip_addr = cur_sp;
+ cache->prev_sp = 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 = op;
+ if (target_read_memory (pc + 1, &op, 1) != 0)
+ return -1;
+
+ if (op >= 0x58 && op <= 0x5f)
+ {
+ /* pop reg */
+ unsigned int reg;
+
+ reg = (op & 0x0f) | ((rex & 1) << 3);
+ cache->prev_reg_addr[amd64_windows_w2gdb_regnum[reg]] = cur_sp;
+ cur_sp += 8;
+ }
+ else if (op == 0xff)
+ {
+ /* rex jmp reg */
+ gdb_byte op1;
+ unsigned int reg;
+ gdb_byte buf[8];
+
+ if (target_read_memory (pc + 2, &op1, 1) != 0)
+ return -1;
+ if ((op1 & 0xf8) != 0xe0)
+ return 0;
+ reg = (op1 & 0x0f) | ((rex & 1) << 3);
+
+ get_frame_register (this_frame,
+ amd64_windows_w2gdb_regnum[reg], buf);
+ pc = extract_unsigned_integer (buf, 8, byte_order);
+ }
+ else
+ return 0;
+
+ /* Allow the user to break this loop. */
+ QUIT;
+ }
+}
+
+/* Decode and execute unwind insns at UNWIND_INFO. */
+
+static void
+amd64_windows_frame_decode_insns (struct frame_info *this_frame,
+ struct amd64_windows_frame_cache *cache,
+ CORE_ADDR unwind_info)
+{
+ CORE_ADDR save_addr = 0;
+ CORE_ADDR cur_sp = cache->sp;
+ struct gdbarch *gdbarch = get_frame_arch (this_frame);
+ enum bfd_endian byte_order = gdbarch_byte_order (gdbarch);
+ int j;
+
+ for (j = 0; ; j++)
+ {
+ struct external_pex64_unwind_info ex_ui;
+ /* There are at most 256 16-bit unwind insns. */
+ 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,
+ (gdb_byte *) &ex_ui, sizeof (ex_ui)) != 0)
+ return;
+
+ if (unwind_debug)
+ fprintf_unfiltered
+ (gdb_stdlog,
+ "amd64_windows_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) != 1)
+ return;
+
+ if (j == 0
+ && (cache->pc >=
+ cache->image_base + cache->start_address + ex_ui.SizeOfPrologue))
+ {
+ /* Not in the prologue; try to decode an epilog. */
+ if (amd64_windows_frame_decode_epilogue (this_frame, cache) == 1)
+ return;
+
+ /* Not in an epilog. Clear possible side effects. */
+ memset (cache->prev_reg_addr, 0, sizeof (cache->prev_reg_addr));
+ }
+
+ codes_count = ex_ui.CountOfCodes;
+ frame_reg = PEX64_UWI_FRAMEREG (ex_ui.FrameRegisterOffset);
+
+ if (frame_reg != 0)
+ {
+ /* According to msdn:
+ If an FP reg is used, then any unwind code taking an offset must
+ only be used after the FP reg is established in the prolog. */
+ gdb_byte buf[8];
+ int frreg = amd64_windows_w2gdb_regnum[frame_reg];
+
+ get_frame_register (this_frame, frreg, buf);
+ save_addr = extract_unsigned_integer (buf, 8, byte_order);
+
+ if (unwind_debug)
+ fprintf_unfiltered (gdb_stdlog, " frame_reg=%s, val=%s\n",
+ gdbarch_register_name (gdbarch, frreg),
+ paddress (gdbarch, save_addr));
+ }
+
+ /* Read opcodes. */
+ if (codes_count != 0
+ && target_read_memory (cache->image_base + unwind_info
+ + sizeof (ex_ui),
+ insns, codes_count * 2) != 0)
+ return;
+
+ end_insns = &insns[codes_count * 2];
+ for (p = insns; p < end_insns; p += 2)
+ {
+ int reg;
+
+ if (unwind_debug)
+ fprintf_unfiltered
+ (gdb_stdlog, " op #%u: off=0x%02x, insn=0x%02x\n",
+ (unsigned) (p - insns), p[0], p[1]);
+
+ /* Virtually execute the operation. */
+ if (cache->pc >= 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 == 0)
+ save_addr = cur_sp;
+
+ switch (PEX64_UNWCODE_CODE (p[1]))
+ {
+ case UWOP_PUSH_NONVOL:
+ /* Push pre-decrements RSP. */
+ reg = amd64_windows_w2gdb_regnum[PEX64_UNWCODE_INFO (p[1])];
+ cache->prev_reg_addr[reg] = cur_sp;
+ cur_sp += 8;
+ break;
+ case UWOP_ALLOC_LARGE:
+ if (PEX64_UNWCODE_INFO (p[1]) == 0)
+ cur_sp +=
+ 8 * extract_unsigned_integer (p + 2, 2, byte_order);
+ else if (PEX64_UNWCODE_INFO (p[1]) == 1)
+ cur_sp += extract_unsigned_integer (p + 2, 4, byte_order);
+ else
+ return;
+ break;
+ case UWOP_ALLOC_SMALL:
+ cur_sp += 8 + 8 * PEX64_UNWCODE_INFO (p[1]);
+ break;
+ case UWOP_SET_FPREG:
+ cur_sp = save_addr
+ - PEX64_UWI_FRAMEOFF (ex_ui.FrameRegisterOffset) * 16;
+ break;
+ case UWOP_SAVE_NONVOL:
+ reg = amd64_windows_w2gdb_regnum[PEX64_UNWCODE_INFO (p[1])];
+ cache->prev_reg_addr[reg] = save_addr
+ + 8 * extract_unsigned_integer (p + 2, 2, byte_order);
+ break;
+ case UWOP_SAVE_NONVOL_FAR:
+ reg = amd64_windows_w2gdb_regnum[PEX64_UNWCODE_INFO (p[1])];
+ cache->prev_reg_addr[reg] = 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])] =
+ 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])] =
+ save_addr
+ + 8 * extract_unsigned_integer (p + 2, 4, byte_order);
+ break;
+ case UWOP_PUSH_MACHFRAME:
+ if (PEX64_UNWCODE_INFO (p[1]) == 0)
+ {
+ cache->prev_rip_addr = cur_sp + 0;
+ cache->prev_rsp_addr = cur_sp + 24;
+ cur_sp += 40;
+ }
+ else if (PEX64_UNWCODE_INFO (p[1]) == 1)
+ {
+ cache->prev_rip_addr = cur_sp + 8;
+ cache->prev_rsp_addr = cur_sp + 32;
+ cur_sp += 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]) == 0)
+ p += 2;
+ else if (PEX64_UNWCODE_INFO (p[1]) == 1)
+ p += 4;
+ else
+ return;
+ break;
+ case UWOP_SAVE_NONVOL:
+ case UWOP_SAVE_XMM128:
+ p += 2;
+ break;
+ case UWOP_SAVE_NONVOL_FAR:
+ case UWOP_SAVE_XMM128_FAR:
+ p += 4;
+ break;
+ default:
+ return;
+ }
+ }
+ if (PEX64_UWI_FLAGS (ex_ui.Version_Flags) != UNW_FLAG_CHAININFO)
+ break;
+ else
+ {
+ /* Read the chained unwind info. */
+ struct external_pex64_runtime_function d;
+ CORE_ADDR chain_vma;
+
+ chain_vma = cache->image_base + unwind_info
+ + sizeof (ex_ui) + ((codes_count + 1) & ~1) * 2 + 8;
+
+ if (target_read_memory (chain_vma, (gdb_byte *) &d, sizeof (d)) != 0)
+ return;
+
+ cache->start_address =
+ extract_unsigned_integer (d.rva_BeginAddress, 4, byte_order);
+ unwind_info =
+ extract_unsigned_integer (d.rva_EndAddress, 4, byte_order);
+ }
+
+ /* Allow the user to break this loop. */
+ QUIT;
+ }
+ /* PC is saved by the call. */
+ if (cache->prev_rip_addr == 0)
+ cache->prev_rip_addr = cur_sp;
+ cache->prev_sp = 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));
+}
+
+/* Do all the work: fill THIS_CACHE with unwinding data for THIS_FRAME. */
+
+static struct amd64_windows_frame_cache *
+amd64_windows_frame_cache (struct frame_info *this_frame, void **this_cache)
+{
+ struct gdbarch *gdbarch = get_frame_arch (this_frame);
+ enum bfd_endian byte_order = gdbarch_byte_order (gdbarch);
+ struct amd64_windows_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 = FRAME_OBSTACK_ZALLOC (struct amd64_windows_frame_cache);
+ *this_cache = cache;
+
+ /* Get current PC and SP. */
+ pc = get_frame_pc (this_frame);
+ get_frame_register (this_frame, AMD64_RSP_REGNUM, buf);
+ cache->sp = extract_unsigned_integer (buf, 8, byte_order);
+ cache->pc = pc;
+
+ /* Get the corresponding exception directory. */
+ sec = find_pc_section (pc);
+ if (unwind_debug)
+ fprintf_unfiltered (gdb_stdlog,
+ "amd64_windows_frame_cache: pc=%s, sp=%s, sec=%p\n",
+ paddress (gdbarch, pc),
+ paddress (gdbarch, cache->sp), sec);
+ if (sec == NULL)
+ return cache;
+ objfile = sec->objfile;
+ pe = pe_data (sec->objfile->obfd);
+ dir = &pe->pe_opthdr.DataDirectory[PE_EXCEPTION_TABLE];
+
+ image_base = pe->pe_opthdr.ImageBase
+ + ANOFFSET (objfile->section_offsets, SECT_OFF_TEXT (objfile));
+ cache->image_base = image_base;
+
+ if (unwind_debug)
+ fprintf_unfiltered (gdb_stdlog,
+ "amd64_windows_frame_cache: file:%s, image_base=%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 = 0;
+ hi = dir->Size / sizeof (struct external_pex64_runtime_function);
+ unwind_info = 0;
+ while (lo <= hi)
+ {
+ unsigned long mid = 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)) != 0)
+ return cache;
+
+ sa = extract_unsigned_integer (d.rva_BeginAddress, 4, byte_order);
+ ea = extract_unsigned_integer (d.rva_EndAddress, 4, byte_order);
+ if (pc < image_base + sa)
+ hi = mid - 1;
+ else if (pc >= image_base + ea)
+ lo = mid + 1;
+ else if (pc >= image_base + sa && pc < image_base + ea)
+ {
+ /* Got it. */
+ cache->start_address = sa;
+ unwind_info =
+ extract_unsigned_integer (d.rva_UnwindData, 4, byte_order);
+ break;
+ }
+ else
+ break;
+ }
+
+ if (unwind_debug)
+ fprintf_unfiltered
+ (gdb_stdlog,
+ "amd64_windows_frame_cache: image_base=%s, unwind_data=%s\n",
+ paddress (gdbarch, cache->image_base), paddress (gdbarch, unwind_info));
+
+ if (unwind_info == 0)
+ {
+ /* Assume a leaf function. */
+ cache->prev_sp = cache->sp + 8;
+ cache->prev_rip_addr = 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)) != 0)
+ return cache;
+
+ cache->start_address =
+ extract_unsigned_integer (d.rva_BeginAddress, 4, byte_order);
+ unwind_info =
+ extract_unsigned_integer (d.rva_EndAddress, 4, byte_order);
+ }
+
+ /* Decode unwind insns to compute saved addresses. */
+ amd64_windows_frame_decode_insns (this_frame, cache, unwind_info);
+ }
+ return cache;
+}
+
+/* Get the value of register REGNUM before the execution of code for
+ THIS_FRAME, using THIS_CACHE data. */
+
+static struct value *
+amd64_windows_frame_prev_register (struct frame_info *this_frame,
+ void **this_cache, int regnum)
+{
+ struct gdbarch *gdbarch = get_frame_arch (this_frame);
+ enum bfd_endian byte_order = gdbarch_byte_order (gdbarch);
+ struct amd64_windows_frame_cache *cache =
+ amd64_windows_frame_cache (this_frame, this_cache);
+ struct value *val;
+ CORE_ADDR prev;
+
+ if (unwind_debug)
+ fprintf_unfiltered (gdb_stdlog,
+ "amd64_windows_frame_prev_register %s for sp=%s\n",
+ gdbarch_register_name (gdbarch, regnum),
+ paddress (gdbarch, cache->prev_sp));
+
+ if (regnum >= AMD64_XMM0_REGNUM && regnum <= AMD64_XMM0_REGNUM + 15)
+ prev = cache->prev_xmm_addr[regnum - AMD64_XMM0_REGNUM];
+ else if (regnum == AMD64_RSP_REGNUM)
+ {
+ prev = cache->prev_rsp_addr;
+ if (prev == 0)
+ return frame_unwind_got_constant (this_frame, regnum, cache->prev_sp);
+ }
+ else if (regnum >= AMD64_RAX_REGNUM && regnum <= AMD64_R15_REGNUM)
+ prev = cache->prev_reg_addr[regnum - AMD64_RAX_REGNUM];
+ else if (regnum == AMD64_RIP_REGNUM)
+ prev = cache->prev_rip_addr;
+ else
+ prev = 0;
+
+ if (prev && unwind_debug)
+ fprintf_unfiltered (gdb_stdlog, " -> at %s\n", paddress (gdbarch, prev));
+
+ if (prev)
+ {
+ /* Register was saved. */
+ return frame_unwind_got_memory (this_frame, regnum, prev);
+ }
+ else
+ {
+ /* Register is either volatile or not modified. */
+ return frame_unwind_got_register (this_frame, regnum, regnum);
+ }
+}
+
+/* Compute and store the frame identifier to THIS_ID for THIS_FRAME using
+ cached data in THIS_CACHE. */
+
+static void
+amd64_windows_frame_this_id (struct frame_info *this_frame, void **this_cache,
+ struct frame_id *this_id)
+{
+ struct gdbarch *gdbarch = get_frame_arch (this_frame);
+ struct amd64_windows_frame_cache *cache =
+ amd64_windows_frame_cache (this_frame, this_cache);
+
+ *this_id = frame_id_build (cache->prev_sp,
+ cache->image_base + cache->start_address);
+}
+
+/* Windows x64 SEH unwinder. */
+
+static const struct frame_unwind amd64_windows_frame_unwind =
+{
+ NORMAL_FRAME,
+ default_frame_unwind_stop_reason,
+ &amd64_windows_frame_this_id,
+ &amd64_windows_frame_prev_register,
+ NULL,
+ default_frame_sniffer
+};
static void
amd64_windows_init_abi (struct gdbarch_info info, struct gdbarch *gdbarch)
@@ -174,6 +794,8 @@ amd64_windows_init_abi (struct gdbarch_info info, struct gdbarch *gdbarch)
set_gdbarch_return_value (gdbarch, amd64_windows_return_value);
set_gdbarch_skip_main_prologue (gdbarch, amd64_skip_main_prologue);
+ frame_unwind_prepend_unwinder (gdbarch, &amd64_windows_frame_unwind);
+
set_solib_ops (gdbarch, &solib_target_so_ops);
}
^ permalink raw reply [flat|nested] 7+ messages in thread* Re: [RFA] Add Windows x64 SEH unwinder
2012-06-18 9:18 ` Tristan Gingold
@ 2012-06-18 16:12 ` Eli Zaretskii
2012-06-20 14:48 ` Joel Brobecker
1 sibling, 0 replies; 7+ messages in thread
From: Eli Zaretskii @ 2012-06-18 16:12 UTC (permalink / raw)
To: Tristan Gingold; +Cc: brobecker, gdb-patches, ktietz70, mark.kettenis
> From: Tristan Gingold <gingold@adacore.com>
> Date: Mon, 18 Jun 2012 11:17:47 +0200
> Cc: "gdb-patches@sourceware.org ml" <gdb-patches@sourceware.org>, Kai Tietz <ktietz70@googlemail.com>, Mark Kettenis <mark.kettenis@xs4all.nl>
>
> +* GDB can now use Windows x64 unwinding data.
This is GDB hacker oriented, not user-oriented. I believe your
original message included a much more user-friendly summary of this
change; how about using it in NEWS?
Thanks.
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [RFA] Add Windows x64 SEH unwinder
2012-06-18 9:18 ` Tristan Gingold
2012-06-18 16:12 ` Eli Zaretskii
@ 2012-06-20 14:48 ` Joel Brobecker
1 sibling, 0 replies; 7+ messages in thread
From: Joel Brobecker @ 2012-06-20 14:48 UTC (permalink / raw)
To: Tristan Gingold; +Cc: gdb-patches@sourceware.org ml, Kai Tietz, Mark Kettenis
> 2012-06-15 Tristan Gingold <gingold@adacore.com>
>
> * 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.
> * NEWS: Mention it.
Just for the record, Tristan and I found a weakness in this approach
which causes regressions for leaf functions when the compiler is not
configured to produce native SEH info (IIUC). My understanding is that,
the problem is that there is no entry for a function if the function
is a leaf function. So, for the case of such function, we don't know
if there is no info because it is a leaf function, or because it wasn't
going to be provided in the first place.
We discussed this situation, and Tristan proposed to make the DWARF
unwinder take priority over the native SEH info. I think that's
a good idea, in any case.
I also see that Tristan made some changes in our tree today, providing
a prologue-skipping routine, but I think that this is for a different
issue (I haven't had a chance to talk to him yet).
--
Joel
^ permalink raw reply [flat|nested] 7+ messages in thread
end of thread, other threads:[~2012-06-20 14:48 UTC | newest]
Thread overview: 7+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2012-06-15 8:10 [RFA] Add Windows x64 SEH unwinder Tristan Gingold
2012-06-15 18:06 ` Joel Brobecker
2012-06-18 9:23 ` Tristan Gingold
2012-06-16 10:40 ` Mark Kettenis
2012-06-18 9:18 ` Tristan Gingold
2012-06-18 16:12 ` Eli Zaretskii
2012-06-20 14:48 ` Joel Brobecker
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox