2009-01-22 Zoltan Varga <vargaz@gmail.com>
[mono.git] / mono / mini / unwind.c
1 /*
2  * unwind.c: Stack Unwinding Interface
3  *
4  * Authors:
5  *   Zoltan Varga (vargaz@gmail.com)
6  *
7  * (C) 2008 Novell, Inc.
8  */
9
10 #include "mini.h"
11 #include "unwind.h"
12
13 typedef enum {
14         LOC_SAME,
15         LOC_OFFSET
16 } LocType;
17
18 typedef struct {
19         LocType loc_type;
20         int offset;
21 } Loc;
22
23 #ifdef __x86_64__
24 static int map_hw_reg_to_dwarf_reg [] = { 0, 2, 1, 3, 7, 6, 4, 5, 8, 9, 10, 11, 12, 13, 14, 15, 16 };
25 #define NUM_REGS AMD64_NREG
26 #else
27 #define NUM_REGS 0
28 #endif
29
30 static gboolean dwarf_reg_to_hw_reg_inited;
31
32 static int map_dwarf_reg_to_hw_reg [NUM_REGS];
33
34 /*
35  * mono_hw_reg_to_dwarf_reg:
36  *
37  *   Map the hardware register number REG to the register number used by DWARF.
38  */
39 int
40 mono_hw_reg_to_dwarf_reg (int reg)
41 {
42 #ifdef __x86_64__
43         return map_hw_reg_to_dwarf_reg [reg];
44 #else
45         g_assert_not_reached ();
46         return -1;
47 #endif
48 }
49
50 static void
51 init_reg_map (void)
52 {
53         int i;
54
55         g_assert (sizeof (map_hw_reg_to_dwarf_reg) / sizeof (int) == NUM_REGS);
56         for (i = 0; i < NUM_REGS; ++i) {
57                 map_dwarf_reg_to_hw_reg [mono_hw_reg_to_dwarf_reg (i)] = i;
58         }
59
60         mono_memory_barrier ();
61         dwarf_reg_to_hw_reg_inited = TRUE;
62 }
63
64 static inline int
65 mono_dwarf_reg_to_hw_reg (int reg)
66 {
67         if (!dwarf_reg_to_hw_reg_inited)
68                 init_reg_map ();
69
70         return map_dwarf_reg_to_hw_reg [reg];
71 }
72
73 static G_GNUC_UNUSED void
74 encode_uleb128 (guint32 value, guint8 *buf, guint8 **endbuf)
75 {
76         guint8 *p = buf;
77
78         do {
79                 guint8 b = value & 0x7f;
80                 value >>= 7;
81                 if (value != 0) /* more bytes to come */
82                         b |= 0x80;
83                 *p ++ = b;
84         } while (value);
85
86         *endbuf = p;
87 }
88
89 static inline guint32
90 decode_uleb128 (guint8 *buf, guint8 **endbuf)
91 {
92         guint8 *p = buf;
93         guint32 res = 0;
94         int shift = 0;
95
96         while (TRUE) {
97                 guint8 b = *p;
98                 p ++;
99
100                 res = res | (((int)(b & 0x7f)) << shift);
101                 if (!(b & 0x80))
102                         break;
103                 shift += 7;
104         }
105
106         *endbuf = p;
107
108         return res;
109 }
110
111 /*
112  * mono_unwind_ops_encode:
113  *
114  *   Encode the unwind ops in UNWIND_OPS into the compact DWARF encoding.
115  * Return a pointer to malloc'ed memory.
116  */
117 guint8*
118 mono_unwind_ops_encode (GSList *unwind_ops, guint32 *out_len)
119 {
120         GSList *l;
121         MonoUnwindOp *op;
122         int loc;
123         guint8 *buf, *p, *res;
124
125         p = buf = g_malloc0 (256);
126
127         loc = 0;
128         l = unwind_ops;
129         for (; l; l = l->next) {
130                 int reg;
131
132                 op = l->data;
133
134                 /* Convert the register from the hw encoding to the dwarf encoding */
135                 reg = mono_hw_reg_to_dwarf_reg (op->reg);
136
137                 /* Emit an advance_loc if neccesary */
138                 if (op->when > loc) {
139                         g_assert (op->when - loc < 32);
140                         *p ++ = DW_CFA_advance_loc | (op->when - loc);
141                 }                       
142
143                 switch (op->op) {
144                 case DW_CFA_def_cfa:
145                         *p ++ = op->op;
146                         encode_uleb128 (reg, p, &p);
147                         encode_uleb128 (op->val, p, &p);
148                         break;
149                 case DW_CFA_def_cfa_offset:
150                         *p ++ = op->op;
151                         encode_uleb128 (op->val, p, &p);
152                         break;
153                 case DW_CFA_def_cfa_register:
154                         *p ++ = op->op;
155                         encode_uleb128 (reg, p, &p);
156                         break;
157                 case DW_CFA_offset:
158                         *p ++ = DW_CFA_offset | reg;
159                         encode_uleb128 (op->val / - 8, p, &p);
160                         break;
161                 default:
162                         g_assert_not_reached ();
163                         break;
164                 }
165
166                 loc = op->when;
167         }
168         
169         g_assert (p - buf < 256);
170         *out_len = p - buf;
171         res = g_malloc (p - buf);
172         memcpy (res, buf, p - buf);
173         g_free (buf);
174         return res;
175 }
176
177 #if 0
178 #define UNW_DEBUG(stmt) do { stmt; } while (0)
179 #else
180 #define UNW_DEBUG(stmt) do { } while (0)
181 #endif
182
183 static G_GNUC_UNUSED void
184 print_dwarf_state (int cfa_reg, int cfa_offset, int ip, int nregs, Loc *locations)
185 {
186         int i;
187
188         printf ("\t%x: cfa=r%d+%d ", ip, cfa_reg, cfa_offset);
189
190         for (i = 0; i < nregs; ++i)
191                 if (locations [i].loc_type == LOC_OFFSET)
192                         printf ("r%d@%d(cfa) ", i, locations [i].offset);
193         printf ("\n");
194 }
195
196 /*
197  * Given the state of the current frame as stored in REGS, execute the unwind 
198  * operations in unwind_info until the location counter reaches POS. The result is 
199  * stored back into REGS. OUT_CFA will receive the value of the CFA.
200  */
201 void
202 mono_unwind_frame (guint8 *unwind_info, guint32 unwind_info_len, 
203                                    int data_align_factor,
204                                    guint8 *start_ip, guint8 *end_ip, guint8 *ip, gssize *regs, 
205                                    int nregs, guint8 **out_cfa) 
206 {
207         Loc locations [NUM_REGS];
208         int i, pos, reg, cfa_reg, cfa_offset;
209         guint8 *p;
210         guint8 *cfa_val;
211
212         g_assert (nregs <= NUM_REGS);
213
214         for (i = 0; i < nregs; ++i)
215                 locations [i].loc_type = LOC_SAME;
216
217         p = unwind_info;
218         pos = 0;
219         while (pos < ip - start_ip && p < unwind_info + unwind_info_len) {
220                 int op = *p & 0xc0;
221
222                 switch (op) {
223                 case DW_CFA_advance_loc:
224                         UNW_DEBUG (print_dwarf_state (cfa_reg, cfa_offset, pos, nregs, locations));
225                         pos += *p & 0x3f;
226                         p ++;
227                         break;
228                 case DW_CFA_offset:
229                         reg = mono_dwarf_reg_to_hw_reg (*p & 0x3f);
230                         p ++;
231                         locations [reg].loc_type = LOC_OFFSET;
232                         locations [reg].offset = decode_uleb128 (p, &p) * data_align_factor;
233                         break;
234                 case 0: {
235                         int ext_op = *p;
236                         p ++;
237                         switch (ext_op) {
238                         case DW_CFA_def_cfa:
239                                 cfa_reg = mono_dwarf_reg_to_hw_reg (decode_uleb128 (p, &p));
240                                 cfa_offset = decode_uleb128 (p, &p);
241                                 break;
242                         case DW_CFA_def_cfa_offset:
243                                 cfa_offset = decode_uleb128 (p, &p);
244                                 break;
245                         case DW_CFA_def_cfa_register:
246                                 cfa_reg = mono_dwarf_reg_to_hw_reg (decode_uleb128 (p, &p));
247                                 break;
248                         default:
249                                 g_assert_not_reached ();
250                         }
251                         break;
252                 }
253                 default:
254                         g_assert_not_reached ();
255                 }
256         }
257
258         cfa_val = (guint8*)regs [cfa_reg] + cfa_offset;
259         for (i = 0; i < nregs; ++i) {
260                 if (locations [i].loc_type == LOC_OFFSET)
261                         regs [i] = *(gssize*)(cfa_val + locations [i].offset);
262         }
263
264         *out_cfa = cfa_val;
265 }