From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: (qmail 16322 invoked by alias); 16 Jun 2012 10:40:27 -0000 Received: (qmail 16308 invoked by uid 22791); 16 Jun 2012 10:40:25 -0000 X-SWARE-Spam-Status: No, hits=-2.8 required=5.0 tests=AWL,BAYES_00,KHOP_THREADED,TW_BJ,TW_QW,T_RP_MATCHES_RCVD X-Spam-Check-By: sourceware.org Received: from sibelius.xs4all.nl (HELO glazunov.sibelius.xs4all.nl) (83.163.83.176) by sourceware.org (qpsmtpd/0.43rc1) with ESMTP; Sat, 16 Jun 2012 10:40:09 +0000 Received: from glazunov.sibelius.xs4all.nl (kettenis@localhost [127.0.0.1]) by glazunov.sibelius.xs4all.nl (8.14.5/8.14.3) with ESMTP id q5GAdhZP029475; Sat, 16 Jun 2012 12:39:43 +0200 (CEST) Received: (from kettenis@localhost) by glazunov.sibelius.xs4all.nl (8.14.5/8.14.3/Submit) id q5GAdfST016307; Sat, 16 Jun 2012 12:39:41 +0200 (CEST) Date: Sat, 16 Jun 2012 10:40:00 -0000 Message-Id: <201206161039.q5GAdfST016307@glazunov.sibelius.xs4all.nl> From: Mark Kettenis To: gingold@adacore.com CC: gdb-patches@sourceware.org, ktietz70@googlemail.com In-reply-to: (message from Tristan Gingold on Fri, 15 Jun 2012 10:09:56 +0200) Subject: Re: [RFA] Add Windows x64 SEH unwinder References: 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/msg00538.txt.bz2 > From: Tristan Gingold > 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); > } > > > >