summaryrefslogtreecommitdiffstats
path: root/0001-Teach-gdb-how-to-unwind-cygwin-_sigbe-and-sigdelayed.patch
blob: 9cee76775855eb8c55466fe54518adafa5198854 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
From 3b165f5d903053172dfb2182eea98595b73fd8d1 Mon Sep 17 00:00:00 2001
From: Jon Turney <jon.turney@dronecode.org.uk>
Date: Tue, 12 Jan 2016 22:49:09 +0000
Subject: [PATCH 1/3] Teach gdb how to unwind cygwin _sigbe and sigdelayed
 frames

The majority of functions in the cygwin DLL are wrapped by routines which use an
an alternate stack to return via a signal handler if a signal occured while
inside the function. (See [1],[2])

At present, these frames cannot be correctly unwound by gdb.  There doesn't seem
to currently be a way to correctly describe these frames using DWARF CFI.

So instead, write a custom unwinder for _sigbe and sigdelayed frames, which gets
the return address from the alternate stack.

The offset of tls::stackptr from TIB.stacktop is determined by analyzing the
code in _sigbe or sigdelayed.

This can backtrace from _sigbe and from a sighandler through sigdelayed.

Implemented for amd64 and i386

Issues:

1. We should detect if we are in the wrapper after the return address has been
popped off the alternate stack, and if so, fetch the return address from the
register it's been popped into.

2. If there are multiple _sigbe or sigdelayed stack frames to be unwound, this
only unwinds the first one correctly, because we don't unwind the value of the
alternate stack pointer itself.

This is no worse than currently, when we can't even unwind one of these frame
correctly, but isn't quite correct.

I guess this could be handled by defining a pseduo-register to track it's value
as we unwind the stack.

[1] https://sourceware.org/git/gitweb.cgi?p=newlib-cygwin.git;a=blob;f=winsup/cygwin/gendef
[2] https://sourceware.org/git/gitweb.cgi?p=newlib-cygwin.git;a=blob;f=winsup/cygwin/how-signals-work.txt
---
 gdb/amd64-windows-tdep.c |  57 ++++++++++++
 gdb/i386-cygwin-tdep.c   |  28 ++++++
 gdb/windows-tdep.c       | 237 +++++++++++++++++++++++++++++++++++++++++++++++
 gdb/windows-tdep.h       |  21 +++++
 4 files changed, 343 insertions(+)

diff --git a/gdb/amd64-windows-tdep.c b/gdb/amd64-windows-tdep.c
index a092298..c9b9d20 100644
--- a/gdb/amd64-windows-tdep.c
+++ b/gdb/amd64-windows-tdep.c
@@ -1218,11 +1218,68 @@ amd64_windows_auto_wide_charset (void)
   return "UTF-16";
 }
 
+static const struct insn_pattern amd64_sigbe_bytes[] = {
+  /* movq	$-8,%r11 */
+  { 0x49, 0xff },
+  { 0xc7, 0xff },
+  { 0xc3, 0xff },
+  { 0xf8, 0xff },
+  { 0xff, 0xff },
+  { 0xff, 0xff },
+  { 0xff, 0xff },
+  /* xaddq	%r11,$tls::stackptr(%r10) */
+  { 0x4d, 0xff },
+  { 0x0f, 0xff },
+  { 0xc1, 0xff },
+  { 0x9a, 0xff },
+  { 0x00, 0x00 }, /* 4 bytes for tls::stackptr */
+  { 0x00, 0x00 },
+  { 0x00, 0x00 },
+  { 0x00, 0x00 }
+};
+
+static const struct insn_pattern amd64_sigdelayed_bytes[] = {
+  /* movq	$-8,%r11 */
+  { 0x49, 0xff },
+  { 0xc7, 0xff },
+  { 0xc3, 0xff },
+  { 0xf8, 0xff },
+  { 0xff, 0xff },
+  { 0xff, 0xff },
+  { 0xff, 0xff },
+  /* xaddq	%r11,$tls::stackptr(%r12) */
+  { 0x4d, 0xff },
+  { 0x0f, 0xff },
+  { 0xc1, 0xff },
+  { 0x9c, 0xff },
+  { 0x24, 0xff },
+  { 0x00, 0x00 }, /* 4 bytes for tls::stackptr */
+  { 0x00, 0x00 },
+  { 0x00, 0x00 },
+  { 0x00, 0x00 }
+};
+
+#define COUNT(x) (sizeof(x)/sizeof(x[0]))
+
+static const struct insn_pattern_sequence amd64_sigbe =
+  {
+    amd64_sigbe_bytes, COUNT(amd64_sigbe_bytes)
+  };
+
+static const struct insn_pattern_sequence amd64_sigdelayed =
+  {
+    amd64_sigdelayed_bytes, COUNT(amd64_sigdelayed_bytes)
+  };
+
 static void
 amd64_windows_init_abi (struct gdbarch_info info, struct gdbarch *gdbarch)
 {
   struct gdbarch_tdep *tdep = gdbarch_tdep (gdbarch);
 
+  cygwin_sigwrapper_frame_unwind_set_sigbe_pattern (&amd64_sigbe);
+  cygwin_sigwrapper_frame_unwind_set_sigdelayed_pattern (&amd64_sigdelayed);
+  frame_unwind_append_unwinder (gdbarch, &cygwin_sigwrapper_frame_unwind);
+
   /* The dwarf2 unwinder (appended very early by i386_gdbarch_init) is
      preferred over the SEH one.  The reasons are:
      - binaries without SEH but with dwarf2 debug info are correcly handled
diff --git a/gdb/i386-cygwin-tdep.c b/gdb/i386-cygwin-tdep.c
index 79ff478..4fc4686 100644
--- a/gdb/i386-cygwin-tdep.c
+++ b/gdb/i386-cygwin-tdep.c
@@ -26,6 +26,7 @@
 #include "xml-support.h"
 #include "gdbcore.h"
 #include "inferior.h"
+#include "frame-unwind.h"
 
 /* Core file support.  */
 
@@ -204,11 +205,38 @@ i386_cygwin_auto_wide_charset (void)
   return "UTF-16";
 }
 
+static const struct insn_pattern i386_sigbe_bytes[] = {
+  /* movl       $-4,%eax */
+  { 0xb8, 0xff },
+  { 0xfc, 0xff },
+  { 0xff, 0xff },
+  { 0xff, 0xff },
+  { 0xff, 0xff },
+  /* xadd       %eax,$tls::stackptr(%ebx) */
+  { 0x0f, 0xff },
+  { 0xc1, 0xff },
+  { 0x83, 0xff },
+  { 0x00, 0x00 }, /* 4 bytes for tls::stackptr */
+  { 0x00, 0x00 },
+  { 0x00, 0x00 },
+  { 0x00, 0x00 }
+};
+
+#define COUNT(x) (sizeof(x)/sizeof(x[0]))
+
+static const struct insn_pattern_sequence i386_sigbe =
+  {
+    i386_sigbe_bytes, COUNT(i386_sigbe_bytes)
+  };
+
 static void
 i386_cygwin_init_abi (struct gdbarch_info info, struct gdbarch *gdbarch)
 {
   struct gdbarch_tdep *tdep = gdbarch_tdep (gdbarch);
 
+  cygwin_sigwrapper_frame_unwind_set_sigbe_pattern (&i386_sigbe);
+  frame_unwind_append_unwinder (gdbarch, &cygwin_sigwrapper_frame_unwind);
+
   windows_init_abi (info, gdbarch);
 
   set_gdbarch_skip_trampoline_code (gdbarch, i386_cygwin_skip_trampoline_code);
diff --git a/gdb/windows-tdep.c b/gdb/windows-tdep.c
index dc4e2e4..352df90 100644
--- a/gdb/windows-tdep.c
+++ b/gdb/windows-tdep.c
@@ -33,6 +33,7 @@
 #include "complaints.h"
 #include "solib.h"
 #include "solib-target.h"
+#include "frame-unwind.h"
 #include "gdbcore.h"
 
 struct cmd_list_element *info_w32_cmdlist;
@@ -540,3 +541,239 @@ even if their meaning is unknown."),
      isn't another convenience variable of the same name.  */
   create_internalvar_type_lazy ("_tlb", &tlb_funcs, NULL);
 }
+
+struct cygwin_sigwrapper_frame_cache
+{
+  CORE_ADDR sp;
+  CORE_ADDR pc;
+  CORE_ADDR prev_pc;
+  int tlsoffset;
+};
+
+/* Return non-zero if the instructions at PC match the series
+   described in PATTERN, or zero otherwise.  PATTERN is an array of
+   'struct insn_pattern' objects, terminated by an entry whose mask is
+   zero.
+
+   When the match is successful, fill INSN[i] with what PATTERN[i]
+   matched.  */
+static int
+insns_match_pattern (struct gdbarch *gdbarch, CORE_ADDR pc,
+                     const struct insn_pattern *pattern, int length,
+                     gdb_byte *insn)
+{
+  int i;
+  CORE_ADDR npc = pc;
+
+  for (i = 0; i < length; i++)
+    {
+      gdb_byte buf;
+      target_read_memory (npc, &buf, 1);
+      insn[i] = buf;
+      if ((insn[i] & pattern[i].mask) == pattern[i].data)
+        npc += 1;
+      else
+	return 0;
+    }
+  return 1;
+}
+
+const struct insn_pattern_sequence *sigbe_pattern;
+const struct insn_pattern_sequence *sigdelayed_pattern;
+
+void cygwin_sigwrapper_frame_unwind_set_sigbe_pattern (
+				const struct insn_pattern_sequence *pattern)
+{
+  sigbe_pattern = pattern;
+}
+
+void cygwin_sigwrapper_frame_unwind_set_sigdelayed_pattern (
+				const struct insn_pattern_sequence *pattern)
+{
+  sigdelayed_pattern = pattern;
+}
+
+static void
+cygwin_sigwrapper_frame_analyze (struct gdbarch *gdbarch, CORE_ADDR start,
+				 CORE_ADDR end, int *tlsoffset)
+{
+  enum bfd_endian byte_order = gdbarch_byte_order (gdbarch);
+  CORE_ADDR addr;
+
+  *tlsoffset = 0;
+
+  for (addr = start; addr < end; addr++)
+    {
+      if (sigbe_pattern)
+	{
+	  int len = sigbe_pattern->length;
+	  gdb_byte insn[len];
+
+	  if (insns_match_pattern (gdbarch, addr, sigbe_pattern->pattern,
+				   len, insn))
+	    {
+	      *tlsoffset = extract_signed_integer (&(insn[len - 4]), 4,
+						   byte_order);
+	      break;
+	    }
+	}
+
+      if (sigdelayed_pattern)
+	{
+	  int len = sigdelayed_pattern->length;
+	  gdb_byte insn[len];
+
+	  if (insns_match_pattern (gdbarch, addr, sigdelayed_pattern->pattern,
+				   len, insn))
+	  {
+	    *tlsoffset = extract_signed_integer (&(insn[len - 4]), 4,
+						 byte_order);
+	    break;
+	  }
+	}
+    }
+
+  /*
+    XXX: Perhaps we should also note the address of the xaddq instruction which
+    pops the RA from the sigstack.  If PC is after that, we should look in the
+    appropriate register to get the RA, not on the sigstack.
+  */
+}
+
+/* Fill THIS_CACHE using the cygwin sigwrapper unwinding data
+   for THIS_FRAME.  */
+
+static struct cygwin_sigwrapper_frame_cache *
+cygwin_sigwrapper_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 cygwin_sigwrapper_frame_cache *cache = *this_cache;
+  ptid_t ptid;
+  CORE_ADDR thread_local_base;
+  CORE_ADDR stacktop;
+  CORE_ADDR signalstackptr;
+  CORE_ADDR ra;
+  const int len = gdbarch_addr_bit (gdbarch)/8;
+  gdb_byte buf[len];
+
+  /* Get current PC and SP.  */
+  cache->pc = get_frame_pc (this_frame);
+  get_frame_register (this_frame, gdbarch_sp_regnum(gdbarch), buf);
+  cache->sp = extract_unsigned_integer (buf, len, byte_order);
+
+  /* Get address of top of stack from thread information block */
+  ptid = inferior_ptid;
+  target_get_tib_address (ptid, &thread_local_base);
+
+  read_memory(thread_local_base + len, buf, len);
+  stacktop = extract_unsigned_integer(buf, len, byte_order);
+
+  if (frame_debug)
+    fprintf_unfiltered (gdb_stdlog,
+			"cygwin_sigwrapper_frame_prev_register TEB.stacktop=%s\n",
+			paddress (gdbarch, stacktop ));
+
+  /* Find cygtls, relative to stacktop, and read signalstackptr from cygtls */
+  read_memory(stacktop + cache->tlsoffset, buf, len);
+  signalstackptr = extract_unsigned_integer(buf, len, byte_order);
+
+  if (frame_debug)
+    fprintf_unfiltered (gdb_stdlog,
+			"cygwin_sigwrapper_frame_prev_register sigsp=%s\n",
+			paddress (gdbarch, signalstackptr));
+
+  /* Read return address from signal stack */
+  read_memory (signalstackptr - len, buf, len);
+  cache->prev_pc = extract_unsigned_integer (buf, len, byte_order);
+
+  if (frame_debug)
+    fprintf_unfiltered (gdb_stdlog,
+			"cygwin_sigwrapper_frame_prev_register ra=%s\n",
+			paddress (gdbarch, cache->prev_pc));
+
+  return cache;
+}
+
+static struct value *
+cygwin_sigwrapper_frame_prev_register (struct frame_info *this_frame, void **this_cache,
+					     int regnum)
+{
+  struct gdbarch *gdbarch = get_frame_arch (this_frame);
+  struct cygwin_sigwrapper_frame_cache *cache =
+    cygwin_sigwrapper_frame_cache (this_frame, this_cache);
+
+  if (frame_debug)
+    fprintf_unfiltered (gdb_stdlog,
+			"cygwin_sigwrapper_frame_prev_register %s for pc=%s\n",
+			gdbarch_register_name (gdbarch, regnum),
+			paddress (gdbarch, cache->prev_pc));
+
+  if (regnum == gdbarch_pc_regnum (gdbarch))
+    return frame_unwind_got_address (this_frame, regnum, cache->prev_pc);
+
+  return frame_unwind_got_register (this_frame, regnum, regnum);
+}
+
+static void
+cygwin_sigwrapper_frame_this_id (struct frame_info *this_frame, void **this_cache,
+				      struct frame_id *this_id)
+{
+  *this_id = frame_id_build_unavailable_stack (get_frame_func (this_frame));
+}
+
+static int
+cygwin_sigwrapper_frame_sniffer (const struct frame_unwind *self,
+				       struct frame_info *this_frame,
+				       void **this_cache)
+{
+  struct gdbarch *gdbarch = get_frame_arch (this_frame);
+  struct cygwin_sigwrapper_frame_cache* cache;
+  const char *name;
+  int tlsoffset;
+  CORE_ADDR start, end;
+
+  CORE_ADDR pc = get_frame_pc (this_frame);
+  find_pc_partial_function (pc, &name, &start, &end);
+
+  if (!name)
+    return 0;
+
+  if ((strcmp(name, "_sigbe") != 0) && (strcmp(name, "__sigbe") != 0) &&
+      (strcmp(name, "sigdelayed") != 0) && (strcmp(name, "_sigdelayed") != 0))
+    return 0;
+
+  if (frame_debug)
+    fprintf_unfiltered (gdb_stdlog,
+			"cygwin_sigwrapper_frame_sniffer name=%s, start=%s, end=%s\n",
+			name, paddress (gdbarch, start), paddress (gdbarch, end));
+
+  cygwin_sigwrapper_frame_analyze(gdbarch, start, end, &tlsoffset);
+  if (tlsoffset == 0)
+    return 0;
+
+  if (frame_debug)
+    fprintf_unfiltered (gdb_stdlog,
+			"cygwin_sigwrapper_frame_sniffer sigstackptroffset=%x\n",
+			tlsoffset);
+
+  cache = FRAME_OBSTACK_ZALLOC (struct cygwin_sigwrapper_frame_cache);
+  cache->tlsoffset = tlsoffset;
+
+  *this_cache = cache;
+  cygwin_sigwrapper_frame_cache (this_frame, this_cache);
+
+  return 1;
+}
+
+/* Cygwin sigwapper unwinder.  */
+
+const struct frame_unwind cygwin_sigwrapper_frame_unwind =
+{
+  NORMAL_FRAME,
+  default_frame_unwind_stop_reason,
+  &cygwin_sigwrapper_frame_this_id,
+  &cygwin_sigwrapper_frame_prev_register,
+  NULL,
+  &cygwin_sigwrapper_frame_sniffer
+};
diff --git a/gdb/windows-tdep.h b/gdb/windows-tdep.h
index e1c34c2..b16a64a 100644
--- a/gdb/windows-tdep.h
+++ b/gdb/windows-tdep.h
@@ -32,4 +32,25 @@ extern void windows_xfer_shared_library (const char* so_name,
 
 extern void windows_init_abi (struct gdbarch_info info,
 			      struct gdbarch *gdbarch);
+
+extern const struct frame_unwind cygwin_sigwrapper_frame_unwind;
+
+/* An instruction to match.  */
+struct insn_pattern
+{
+  gdb_byte data;            /* See if it matches this....  */
+  gdb_byte mask;            /* ... with this mask.  */
+};
+
+struct insn_pattern_sequence
+{
+  const struct insn_pattern *pattern;
+  int length;
+};
+
+extern void cygwin_sigwrapper_frame_unwind_set_sigbe_pattern(
+			const struct insn_pattern_sequence *pattern);
+extern void cygwin_sigwrapper_frame_unwind_set_sigdelayed_pattern(
+			 const struct insn_pattern_sequence *pattern);
+
 #endif
-- 
2.6.2