1/* libunwind - a platform-independent unwind library
2   Copyright (C) 2001-2005 Hewlett-Packard Co
3   Copyright (C) 2007 David Mosberger-Tang
4        Contributed by David Mosberger-Tang <dmosberger@gmail.com>
5
6This file is part of libunwind.
7
8Permission is hereby granted, free of charge, to any person obtaining
9a copy of this software and associated documentation files (the
10"Software"), to deal in the Software without restriction, including
11without limitation the rights to use, copy, modify, merge, publish,
12distribute, sublicense, and/or sell copies of the Software, and to
13permit persons to whom the Software is furnished to do so, subject to
14the following conditions:
15
16The above copyright notice and this permission notice shall be
17included in all copies or substantial portions of the Software.
18
19THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
20EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
21MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
22NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
23LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
24OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
25WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.  */
26
27#include "unwind_i.h"
28
29#ifdef HAVE_SYS_UC_ACCESS_H
30# include <sys/uc_access.h>
31#endif
32
33#ifdef UNW_REMOTE_ONLY
34
35/* unw_local_addr_space is a NULL pointer in this case.  */
36PROTECTED unw_addr_space_t unw_local_addr_space;
37
38#else /* !UNW_REMOTE_ONLY */
39
40static struct unw_addr_space local_addr_space;
41
42PROTECTED unw_addr_space_t unw_local_addr_space = &local_addr_space;
43
44#ifdef HAVE_SYS_UC_ACCESS_H
45
46#else /* !HAVE_SYS_UC_ACCESS_H */
47
48HIDDEN void *
49tdep_uc_addr (ucontext_t *uc, int reg, uint8_t *nat_bitnr)
50{
51  return inlined_uc_addr (uc, reg, nat_bitnr);
52}
53
54#endif /* !HAVE_SYS_UC_ACCESS_H */
55
56static void
57put_unwind_info (unw_addr_space_t as, unw_proc_info_t *proc_info, void *arg)
58{
59  /* it's a no-op */
60}
61
62static int
63get_dyn_info_list_addr (unw_addr_space_t as, unw_word_t *dyn_info_list_addr,
64                        void *arg)
65{
66#ifndef UNW_LOCAL_ONLY
67# pragma weak _U_dyn_info_list_addr
68  if (!_U_dyn_info_list_addr)
69    return -UNW_ENOINFO;
70#endif
71  *dyn_info_list_addr = _U_dyn_info_list_addr ();
72  return 0;
73}
74
75static int
76access_mem (unw_addr_space_t as, unw_word_t addr, unw_word_t *val, int write,
77            void *arg)
78{
79  if (write)
80    {
81      Debug (12, "mem[%lx] <- %lx\n", addr, *val);
82      *(unw_word_t *) addr = *val;
83    }
84  else
85    {
86      *val = *(unw_word_t *) addr;
87      Debug (12, "mem[%lx] -> %lx\n", addr, *val);
88    }
89  return 0;
90}
91
92#ifdef HAVE_SYS_UC_ACCESS_H
93
94#define SYSCALL_CFM_SAVE_REG    11 /* on a syscall, ar.pfs is saved in r11 */
95#define REASON_SYSCALL          0
96
97static int
98access_reg (unw_addr_space_t as, unw_regnum_t reg, unw_word_t *val, int write,
99            void *arg)
100{
101  ucontext_t *uc = arg;
102  unsigned int nat, mask;
103  uint64_t value;
104  uint16_t reason;
105  int ret;
106
107  __uc_get_reason (uc, &reason);
108
109  switch (reg)
110    {
111    case UNW_IA64_GR  ... UNW_IA64_GR + 31:
112      if ((ret = __uc_get_grs (uc, (reg - UNW_IA64_GR), 1, &value, &nat)))
113        break;
114
115      if (write)
116        ret = __uc_set_grs (uc, (reg - UNW_IA64_GR), 1, val, nat);
117      else
118        *val = value;
119      break;
120
121    case UNW_IA64_NAT ... UNW_IA64_NAT + 31:
122      if ((ret = __uc_get_grs (uc, (reg - UNW_IA64_GR), 1, &value, &nat)))
123        break;
124
125      mask = 1 << (reg - UNW_IA64_GR);
126
127      if (write)
128        {
129          if (*val)
130            nat |= mask;
131          else
132            nat &= ~mask;
133          ret = __uc_set_grs (uc, (reg - UNW_IA64_GR), 1, &value, nat);
134        }
135      else
136        *val = (nat & mask) != 0;
137      break;
138
139    case UNW_IA64_AR  ... UNW_IA64_AR + 127:
140      if (reg == UNW_IA64_AR_BSP)
141        {
142          if (write)
143            ret = __uc_set_ar (uc, (reg - UNW_IA64_AR), *val);
144          else
145            ret = __uc_get_ar (uc, (reg - UNW_IA64_AR), val);
146        }
147      else if (reg == UNW_IA64_AR_PFS && reason == REASON_SYSCALL)
148        {
149          /* As of HP-UX 11.22, getcontext() does not have unwind info
150             and because of that, we need to hack thins manually here.
151             Hopefully, this is OK because the HP-UX kernel also needs
152             to know where AR.PFS has been saved, so the use of
153             register r11 for this purpose is pretty much nailed
154             down.  */
155          if (write)
156            ret = __uc_set_grs (uc, SYSCALL_CFM_SAVE_REG, 1, val, 0);
157          else
158            ret = __uc_get_grs (uc, SYSCALL_CFM_SAVE_REG, 1, val, &nat);
159        }
160      else
161        {
162          if (write)
163            ret = __uc_set_ar (uc, (reg - UNW_IA64_AR), *val);
164          else
165            ret = __uc_get_ar (uc, (reg - UNW_IA64_AR), val);
166        }
167      break;
168
169    case UNW_IA64_BR  ... UNW_IA64_BR + 7:
170      if (write)
171        ret = __uc_set_brs (uc, (reg - UNW_IA64_BR), 1, val);
172      else
173        ret = __uc_get_brs (uc, (reg - UNW_IA64_BR), 1, val);
174      break;
175
176    case UNW_IA64_PR:
177      if (write)
178        ret = __uc_set_prs (uc, *val);
179      else
180        ret = __uc_get_prs (uc, val);
181      break;
182
183    case UNW_IA64_IP:
184      if (write)
185        ret = __uc_set_ip (uc, *val);
186      else
187        ret = __uc_get_ip (uc, val);
188      break;
189
190    case UNW_IA64_CFM:
191      if (write)
192        ret = __uc_set_cfm (uc, *val);
193      else
194        ret = __uc_get_cfm (uc, val);
195      break;
196
197    case UNW_IA64_FR  ... UNW_IA64_FR + 127:
198    default:
199      ret = EINVAL;
200      break;
201    }
202
203  if (ret != 0)
204    {
205      Debug (1, "failed to %s %s (ret = %d)\n",
206             write ? "write" : "read", unw_regname (reg), ret);
207      return -UNW_EBADREG;
208    }
209
210  if (write)
211    Debug (12, "%s <- %lx\n", unw_regname (reg), *val);
212  else
213    Debug (12, "%s -> %lx\n", unw_regname (reg), *val);
214  return 0;
215}
216
217static int
218access_fpreg (unw_addr_space_t as, unw_regnum_t reg, unw_fpreg_t *val,
219              int write, void *arg)
220{
221  ucontext_t *uc = arg;
222  fp_regval_t fp_regval;
223  int ret;
224
225  switch (reg)
226    {
227    case UNW_IA64_FR  ... UNW_IA64_FR + 127:
228      if (write)
229        {
230          memcpy (&fp_regval, val, sizeof (fp_regval));
231          ret = __uc_set_frs (uc, (reg - UNW_IA64_FR), 1, &fp_regval);
232        }
233      else
234        {
235          ret = __uc_get_frs (uc, (reg - UNW_IA64_FR), 1, &fp_regval);
236          memcpy (val, &fp_regval, sizeof (*val));
237        }
238      break;
239
240    default:
241      ret = EINVAL;
242      break;
243    }
244  if (ret != 0)
245    return -UNW_EBADREG;
246
247  return 0;
248}
249
250#else /* !HAVE_SYS_UC_ACCESS_H */
251
252static int
253access_reg (unw_addr_space_t as, unw_regnum_t reg, unw_word_t *val, int write,
254            void *arg)
255{
256  unw_word_t *addr, mask;
257  ucontext_t *uc = arg;
258
259  if (reg >= UNW_IA64_NAT + 4 && reg <= UNW_IA64_NAT + 7)
260    {
261      mask = ((unw_word_t) 1) << (reg - UNW_IA64_NAT);
262      if (write)
263        {
264          if (*val)
265            uc->uc_mcontext.sc_nat |= mask;
266          else
267            uc->uc_mcontext.sc_nat &= ~mask;
268        }
269      else
270        *val = (uc->uc_mcontext.sc_nat & mask) != 0;
271
272      if (write)
273        Debug (12, "%s <- %lx\n", unw_regname (reg), *val);
274      else
275        Debug (12, "%s -> %lx\n", unw_regname (reg), *val);
276      return 0;
277    }
278
279  addr = tdep_uc_addr (uc, reg, NULL);
280  if (!addr)
281    goto badreg;
282
283  if (write)
284    {
285      if (ia64_read_only_reg (addr))
286        {
287          Debug (16, "attempt to write read-only register\n");
288          return -UNW_EREADONLYREG;
289        }
290      *addr = *val;
291      Debug (12, "%s <- %lx\n", unw_regname (reg), *val);
292    }
293  else
294    {
295      *val = *(unw_word_t *) addr;
296      Debug (12, "%s -> %lx\n", unw_regname (reg), *val);
297    }
298  return 0;
299
300 badreg:
301  Debug (1, "bad register number %u\n", reg);
302  return -UNW_EBADREG;
303}
304
305static int
306access_fpreg (unw_addr_space_t as, unw_regnum_t reg, unw_fpreg_t *val,
307              int write, void *arg)
308{
309  ucontext_t *uc = arg;
310  unw_fpreg_t *addr;
311
312  if (reg < UNW_IA64_FR || reg >= UNW_IA64_FR + 128)
313    goto badreg;
314
315  addr = tdep_uc_addr (uc, reg, NULL);
316  if (!addr)
317    goto badreg;
318
319  if (write)
320    {
321      if (ia64_read_only_reg (addr))
322        {
323          Debug (16, "attempt to write read-only register\n");
324          return -UNW_EREADONLYREG;
325        }
326      *addr = *val;
327      Debug (12, "%s <- %016lx.%016lx\n",
328             unw_regname (reg), val->raw.bits[1], val->raw.bits[0]);
329    }
330  else
331    {
332      *val = *(unw_fpreg_t *) addr;
333      Debug (12, "%s -> %016lx.%016lx\n",
334             unw_regname (reg), val->raw.bits[1], val->raw.bits[0]);
335    }
336  return 0;
337
338 badreg:
339  Debug (1, "bad register number %u\n", reg);
340  /* attempt to access a non-preserved register */
341  return -UNW_EBADREG;
342}
343
344#endif /* !HAVE_SYS_UC_ACCESS_H */
345
346static int
347get_static_proc_name (unw_addr_space_t as, unw_word_t ip,
348                      char *buf, size_t buf_len, unw_word_t *offp,
349                      void *arg)
350{
351  return _Uelf64_get_proc_name (as, getpid (), ip, buf, buf_len, offp);
352}
353
354HIDDEN void
355ia64_local_addr_space_init (void)
356{
357  memset (&local_addr_space, 0, sizeof (local_addr_space));
358  local_addr_space.big_endian = (__BYTE_ORDER == __BIG_ENDIAN);
359#if defined(__linux)
360  local_addr_space.abi = ABI_LINUX;
361#elif defined(__hpux)
362  local_addr_space.abi = ABI_HPUX;
363#endif
364  local_addr_space.caching_policy = UNW_CACHE_GLOBAL;
365  local_addr_space.acc.find_proc_info = tdep_find_proc_info;
366  local_addr_space.acc.put_unwind_info = put_unwind_info;
367  local_addr_space.acc.get_dyn_info_list_addr = get_dyn_info_list_addr;
368  local_addr_space.acc.access_mem = access_mem;
369  local_addr_space.acc.access_reg = access_reg;
370  local_addr_space.acc.access_fpreg = access_fpreg;
371  local_addr_space.acc.resume = ia64_local_resume;
372  local_addr_space.acc.get_proc_name = get_static_proc_name;
373  unw_flush_cache (&local_addr_space, 0, 0);
374}
375
376#endif /* !UNW_REMOTE_ONLY */
377
378#ifndef UNW_LOCAL_ONLY
379
380HIDDEN int
381ia64_uc_access_reg (struct cursor *c, ia64_loc_t loc, unw_word_t *valp,
382                    int write)
383{
384#ifdef HAVE_SYS_UC_ACCESS_H
385  unw_word_t uc_addr = IA64_GET_AUX_ADDR (loc);
386  ucontext_t *ucp;
387  int ret;
388
389  Debug (16, "%s location %s\n",
390         write ? "writing" : "reading", ia64_strloc (loc));
391
392  if (c->as == unw_local_addr_space)
393    ucp = (ucontext_t *) uc_addr;
394  else
395    {
396      unw_word_t *dst, src;
397
398      /* Need to copy-in ucontext_t first.  */
399      ucp = alloca (sizeof (ucontext_t));
400      if (!ucp)
401        return -UNW_ENOMEM;
402
403      /* For now, there is no non-HP-UX implementation of the
404         uc_access(3) interface.  Because of that, we cannot, e.g.,
405         unwind an HP-UX program from a Linux program.  Should that
406         become possible at some point in the future, the
407         copy-in/copy-out needs to be adjusted to do byte-swapping if
408         necessary. */
409      assert (c->as->big_endian == (__BYTE_ORDER == __BIG_ENDIAN));
410
411      dst = (unw_word_t *) ucp;
412      for (src = uc_addr; src < uc_addr + sizeof (ucontext_t); src += 8)
413        if ((ret = (*c->as->acc.access_mem) (c->as, src, dst++, 0, c->as_arg))
414            < 0)
415          return ret;
416    }
417
418  if (IA64_IS_REG_LOC (loc))
419    ret = access_reg (unw_local_addr_space, IA64_GET_REG (loc), valp, write,
420                      ucp);
421  else
422    {
423      /* Must be an access to the RSE backing store in ucontext_t.  */
424      unw_word_t addr = IA64_GET_ADDR (loc);
425
426      if (write)
427        ret = __uc_set_rsebs (ucp, (uint64_t *) addr, 1, valp);
428      else
429        ret = __uc_get_rsebs (ucp, (uint64_t *) addr, 1, valp);
430      if (ret != 0)
431        ret = -UNW_EBADREG;
432    }
433  if (ret < 0)
434    return ret;
435
436  if (write && c->as != unw_local_addr_space)
437    {
438      /* need to copy-out ucontext_t: */
439      unw_word_t dst, *src = (unw_word_t *) ucp;
440      for (dst = uc_addr; dst < uc_addr + sizeof (ucontext_t); dst += 8)
441        if ((ret = (*c->as->acc.access_mem) (c->as, dst, src++, 1, c->as_arg))
442            < 0)
443          return ret;
444    }
445  return 0;
446#else /* !HAVE_SYS_UC_ACCESS_H */
447  return -UNW_EINVAL;
448#endif /* !HAVE_SYS_UC_ACCESS_H */
449}
450
451HIDDEN int
452ia64_uc_access_fpreg (struct cursor *c, ia64_loc_t loc, unw_fpreg_t *valp,
453                      int write)
454{
455#ifdef HAVE_SYS_UC_ACCESS_H
456  unw_word_t uc_addr = IA64_GET_AUX_ADDR (loc);
457  ucontext_t *ucp;
458  int ret;
459
460  if (c->as == unw_local_addr_space)
461    ucp = (ucontext_t *) uc_addr;
462  else
463    {
464      unw_word_t *dst, src;
465
466      /* Need to copy-in ucontext_t first.  */
467      ucp = alloca (sizeof (ucontext_t));
468      if (!ucp)
469        return -UNW_ENOMEM;
470
471      /* For now, there is no non-HP-UX implementation of the
472         uc_access(3) interface.  Because of that, we cannot, e.g.,
473         unwind an HP-UX program from a Linux program.  Should that
474         become possible at some point in the future, the
475         copy-in/copy-out needs to be adjusted to do byte-swapping if
476         necessary. */
477      assert (c->as->big_endian == (__BYTE_ORDER == __BIG_ENDIAN));
478
479      dst = (unw_word_t *) ucp;
480      for (src = uc_addr; src < uc_addr + sizeof (ucontext_t); src += 8)
481        if ((ret = (*c->as->acc.access_mem) (c->as, src, dst++, 0, c->as_arg))
482            < 0)
483          return ret;
484    }
485
486  if ((ret = access_fpreg (unw_local_addr_space, IA64_GET_REG (loc), valp,
487                           write, ucp)) < 0)
488    return ret;
489
490  if (write && c->as != unw_local_addr_space)
491    {
492      /* need to copy-out ucontext_t: */
493      unw_word_t dst, *src = (unw_word_t *) ucp;
494      for (dst = uc_addr; dst < uc_addr + sizeof (ucontext_t); dst += 8)
495        if ((ret = (*c->as->acc.access_mem) (c->as, dst, src++, 1, c->as_arg))
496            < 0)
497          return ret;
498    }
499  return 0;
500#else /* !HAVE_SYS_UC_ACCESS_H */
501  return -UNW_EINVAL;
502#endif /* !HAVE_SYS_UC_ACCESS_H */
503}
504
505#endif /* UNW_LOCAL_ONLY */
506