1/* libunwind - a platform-independent unwind library
2   Copyright (C) 2003-2004 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#ifndef os_linux_h
28#define os_linux_h
29
30#include <sys/mman.h>
31
32struct map_iterator
33  {
34    off_t offset;
35    int fd;
36    size_t buf_size;
37    char *buf;
38    char *buf_end;
39    char *path;
40  };
41
42static inline char *
43ltoa (char *buf, long val)
44{
45  char *cp = buf, tmp;
46  ssize_t i, len;
47
48  do
49    {
50      *cp++ = '0' + (val % 10);
51      val /= 10;
52    }
53  while (val);
54
55  /* reverse the order of the digits: */
56  len = cp - buf;
57  --cp;
58  for (i = 0; i < len / 2; ++i)
59    {
60      tmp = buf[i];
61      buf[i] = cp[-i];
62      cp[-i] = tmp;
63    }
64  return buf + len;
65}
66
67static inline int
68maps_init (struct map_iterator *mi, pid_t pid)
69{
70  char path[sizeof ("/proc/0123456789/maps")], *cp;
71
72  memcpy (path, "/proc/", 6);
73  cp = ltoa (path + 6, pid);
74  assert (cp + 6 < path + sizeof (path));
75  memcpy (cp, "/maps", 6);
76
77  mi->fd = open (path, O_RDONLY);
78  if (mi->fd >= 0)
79    {
80      /* Try to allocate a page-sized buffer.  */
81      mi->buf_size = getpagesize ();
82      cp = mmap (NULL, mi->buf_size, PROT_READ | PROT_WRITE,
83		 MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
84      if (cp == MAP_FAILED)
85	{
86	  close(mi->fd);
87	  mi->fd = -1;
88	  return -1;
89	}
90      else
91	{
92	  mi->offset = 0;
93	  mi->buf = mi->buf_end = cp + mi->buf_size;
94	  return 0;
95	}
96    }
97  return -1;
98}
99
100static inline char *
101skip_whitespace (char *cp)
102{
103  if (!cp)
104    return NULL;
105
106  while (*cp == ' ' || *cp == '\t')
107    ++cp;
108  return cp;
109}
110
111static inline char *
112scan_hex (char *cp, unsigned long *valp)
113{
114  unsigned long num_digits = 0, digit, val = 0;
115
116  cp = skip_whitespace (cp);
117  if (!cp)
118    return NULL;
119
120  while (1)
121    {
122      digit = *cp;
123      if ((digit - '0') <= 9)
124	digit -= '0';
125      else if ((digit - 'a') < 6)
126	digit -= 'a' - 10;
127      else if ((digit - 'A') < 6)
128	digit -= 'A' - 10;
129      else
130	break;
131      val = (val << 4) | digit;
132      ++num_digits;
133      ++cp;
134    }
135  if (!num_digits)
136    return NULL;
137  *valp = val;
138  return cp;
139}
140
141static inline char *
142scan_dec (char *cp, unsigned long *valp)
143{
144  unsigned long num_digits = 0, digit, val = 0;
145
146  if (!(cp = skip_whitespace (cp)))
147    return NULL;
148
149  while (1)
150    {
151      digit = *cp;
152      if ((digit - '0') <= 9)
153	{
154	  digit -= '0';
155	  ++cp;
156	}
157      else
158	break;
159      val = (10 * val) + digit;
160      ++num_digits;
161    }
162  if (!num_digits)
163    return NULL;
164  *valp = val;
165  return cp;
166}
167
168static inline char *
169scan_char (char *cp, char *valp)
170{
171  if (!cp)
172    return NULL;
173
174  *valp = *cp;
175
176  /* don't step over NUL terminator */
177  if (*cp)
178    ++cp;
179  return cp;
180}
181
182/* Scan a string delimited by white-space.  Fails on empty string or
183   if string is doesn't fit in the specified buffer.  */
184static inline char *
185scan_string (char *cp, char *valp, size_t buf_size)
186{
187  size_t i = 0;
188
189  if (!(cp = skip_whitespace (cp)))
190    return NULL;
191
192  while (*cp != ' ' && *cp != '\t' && *cp != '\0')
193    {
194      if ((valp != NULL) && (i < buf_size - 1))
195	valp[i++] = *cp;
196      ++cp;
197    }
198  if (i == 0 || i >= buf_size)
199    return NULL;
200  valp[i] = '\0';
201  return cp;
202}
203
204static inline int
205maps_next (struct map_iterator *mi,
206	   unsigned long *low, unsigned long *high, unsigned long *offset,
207	   unsigned long *flags)
208{
209  char perm[16], dash = 0, colon = 0, *cp;
210  unsigned long major, minor, inum;
211  ssize_t i, nread;
212
213  if (mi->fd < 0)
214    return 0;
215
216  while (1)
217    {
218      ssize_t bytes_left = mi->buf_end - mi->buf;
219      char *eol = NULL;
220
221      for (i = 0; i < bytes_left; ++i)
222	{
223	  if (mi->buf[i] == '\n')
224	    {
225	      eol = mi->buf + i;
226	      break;
227	    }
228	  else if (mi->buf[i] == '\0')
229	    break;
230	}
231      if (!eol)
232	{
233	  /* copy down the remaining bytes, if any */
234	  if (bytes_left > 0)
235	    memmove (mi->buf_end - mi->buf_size, mi->buf, bytes_left);
236
237	  mi->buf = mi->buf_end - mi->buf_size;
238	  nread = read (mi->fd, mi->buf + bytes_left,
239			mi->buf_size - bytes_left);
240	  if (nread <= 0)
241	    return 0;
242	  else if ((size_t) (nread + bytes_left) < mi->buf_size)
243	    {
244	      /* Move contents to the end of the buffer so we
245		 maintain the invariant that all bytes between
246		 mi->buf and mi->buf_end are valid.  */
247	      memmove (mi->buf_end - nread - bytes_left, mi->buf,
248		       nread + bytes_left);
249	      mi->buf = mi->buf_end - nread - bytes_left;
250	    }
251
252	  eol = mi->buf + bytes_left + nread - 1;
253
254	  for (i = bytes_left; i < bytes_left + nread; ++i)
255	    if (mi->buf[i] == '\n')
256	      {
257		eol = mi->buf + i;
258		break;
259	      }
260	}
261      cp = mi->buf;
262      mi->buf = eol + 1;
263      *eol = '\0';
264
265      /* scan: "LOW-HIGH PERM OFFSET MAJOR:MINOR INUM PATH" */
266      cp = scan_hex (cp, low);
267      cp = scan_char (cp, &dash);
268      cp = scan_hex (cp, high);
269      cp = scan_string (cp, perm, sizeof (perm));
270      cp = scan_hex (cp, offset);
271      cp = scan_hex (cp, &major);
272      cp = scan_char (cp, &colon);
273      cp = scan_hex (cp, &minor);
274      cp = scan_dec (cp, &inum);
275      cp = mi->path = skip_whitespace (cp);
276      if (!cp)
277	continue;
278      cp = scan_string (cp, NULL, 0);
279      if (dash != '-' || colon != ':')
280	continue;	/* skip line with unknown or bad format */
281      if (flags)
282        {
283          *flags = 0;
284          if (perm[0] == 'r')
285            {
286              *flags |= PROT_READ;
287            }
288          if (perm[1] == 'w')
289            {
290              *flags |= PROT_WRITE;
291            }
292          if (perm[2] == 'x')
293            {
294              *flags |= PROT_EXEC;
295            }
296        }
297      return 1;
298    }
299  return 0;
300}
301
302static inline void
303maps_close (struct map_iterator *mi)
304{
305  if (mi->fd < 0)
306    return;
307  close (mi->fd);
308  mi->fd = -1;
309  if (mi->buf)
310    {
311      munmap (mi->buf_end - mi->buf_size, mi->buf_size);
312      mi->buf = mi->buf_end = NULL;
313    }
314}
315
316#endif /* os_linux_h */
317