os-linux.h revision af2503e2230f59a8b87ae5b71ef57e7e18b24a98
1/* libunwind - a platform-independent unwind library
2   Copyright (C) 2003-2004 Hewlett-Packard Co
3	Contributed by David Mosberger-Tang <davidm@hpl.hp.com>
4
5This file is part of libunwind.
6
7Permission is hereby granted, free of charge, to any person obtaining
8a copy of this software and associated documentation files (the
9"Software"), to deal in the Software without restriction, including
10without limitation the rights to use, copy, modify, merge, publish,
11distribute, sublicense, and/or sell copies of the Software, and to
12permit persons to whom the Software is furnished to do so, subject to
13the following conditions:
14
15The above copyright notice and this permission notice shall be
16included in all copies or substantial portions of the Software.
17
18THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
19EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
20MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
21NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
22LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
23OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
24WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.  */
25
26#ifndef os_linux_h
27#define os_linux_h
28
29struct map_iterator
30  {
31    off_t offset;
32    int fd;
33    size_t buf_size;
34    char *buf;
35    char *buf_end;
36  };
37
38static inline char *
39ltoa (char *buf, long val)
40{
41  char *cp = buf, tmp;
42  ssize_t i, len;
43
44  do
45    {
46      *cp++ = '0' + (val % 10);
47      val /= 10;
48    }
49  while (val);
50
51  /* reverse the order of the digits: */
52  len = cp - buf;
53  --cp;
54  for (i = 0; i < len / 2; ++i)
55    {
56      tmp = buf[i];
57      buf[i] = cp[-i];
58      cp[-i] = tmp;
59    }
60  return buf + len;
61}
62
63static inline void
64maps_init (struct map_iterator *mi, pid_t pid)
65{
66  char path[PATH_MAX], *cp;
67
68  memcpy (path, "/proc/", 6);
69  cp = ltoa (path + 6, pid);
70  memcpy (cp, "/maps", 6);
71
72  mi->fd = open (path, O_RDONLY);
73  mi->offset = 0;
74
75  cp = NULL;
76  if (mi->fd >= 0)
77    {
78      /* Try to allocate a page-sized buffer.  If that fails, we'll
79	 fall back on reading one line at a time.  */
80      mi->buf_size = getpagesize ();
81      cp = mmap (0, mi->buf_size, PROT_READ | PROT_WRITE,
82		 MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
83      if (cp == MAP_FAILED)
84	cp = NULL;
85      else
86	cp += mi->buf_size;
87    }
88  mi->buf = mi->buf_end = cp;
89}
90
91static inline char *
92skip_whitespace (char *cp)
93{
94  if (!cp)
95    return NULL;
96
97  while (*cp == ' ' || *cp == '\t')
98    ++cp;
99  return cp;
100}
101
102static inline char *
103scan_hex (char *cp, unsigned long *valp)
104{
105  unsigned long num_digits = 0, digit, val = 0;
106
107  cp = skip_whitespace (cp);
108  if (!cp)
109    return NULL;
110
111  while (1)
112    {
113      digit = *cp;
114      if ((digit - '0') <= 9)
115	digit -= '0';
116      else if ((digit - 'a') < 6)
117	digit -= 'a' - 10;
118      else if ((digit - 'A') < 6)
119	digit -= 'A' - 10;
120      else
121	break;
122      val = (val << 4) | digit;
123      ++num_digits;
124      ++cp;
125    }
126  if (!num_digits)
127    return NULL;
128  *valp = val;
129  return cp;
130}
131
132static inline char *
133scan_dec (char *cp, unsigned long *valp)
134{
135  unsigned long num_digits = 0, digit, val = 0;
136
137  if (!(cp = skip_whitespace (cp)))
138    return NULL;
139
140  while (1)
141    {
142      digit = *cp;
143      if ((digit - '0') <= 9)
144	{
145	  digit -= '0';
146	  ++cp;
147	}
148      else
149	break;
150      val = (10 * val) + digit;
151      ++num_digits;
152    }
153  if (!num_digits)
154    return NULL;
155  *valp = val;
156  return cp;
157}
158
159static inline char *
160scan_char (char *cp, char *valp)
161{
162  if (!cp)
163    return NULL;
164
165  *valp = *cp;
166
167  /* don't step over NUL terminator */
168  if (*cp)
169    ++cp;
170  return cp;
171}
172
173/* Scan a string delimited by white-space.  Fails on empty string or
174   if string is doesn't fit in the specified buffer.  */
175static inline char *
176scan_string (char *cp, char *valp, size_t buf_size)
177{
178  size_t i = 0;
179
180  if (!(cp = skip_whitespace (cp)))
181    return NULL;
182
183  while (*cp != ' ' && *cp != '\t' && *cp != '\0')
184    {
185      if (i < buf_size - 1)
186	valp[i++] = *cp;
187      ++cp;
188    }
189  if (i == 0 || i >= buf_size)
190    return NULL;
191  valp[i] = '\0';
192  return cp;
193}
194
195static inline int
196maps_next (struct map_iterator *mi,
197	   unsigned long *low, unsigned long *high, unsigned long *offset,
198	   char *path, size_t path_size)
199{
200  char line[256 + PATH_MAX], perm[16], dash, colon, *cp;
201  unsigned long major, minor, inum;
202  size_t to_read = 256;	/* most lines fit in 256 characters easy */
203  ssize_t i, nread;
204
205  if (mi->fd < 0)
206    return 0;
207
208  while (1)
209    {
210      if (mi->buf)
211	{
212	  ssize_t bytes_left = mi->buf_end - mi->buf;
213	  char *eol = NULL;
214
215	  for (i = 0; i < bytes_left; ++i)
216	    {
217	      if (mi->buf[i] == '\n')
218		{
219		  eol = mi->buf + i;
220		  break;
221		}
222	      else if (mi->buf[i] == '\0')
223		break;
224	    }
225	  if (!eol)
226	    {
227	      /* copy down the remaining bytes, if any */
228	      if (bytes_left > 0)
229		memcpy (mi->buf_end - mi->buf_size, mi->buf, bytes_left);
230
231	      mi->buf = mi->buf_end - mi->buf_size;
232	      nread = read (mi->fd, mi->buf + bytes_left,
233			    mi->buf_size - bytes_left);
234	      if (nread <= 0)
235		return 0;
236	      else if (nread + bytes_left < mi->buf_size)
237		{
238		  /* Move contents to the end of the buffer so we
239		     maintain the invariant that all bytes between
240		     mi->buf and mi->buf_end are valid.  */
241		  memcpy (mi->buf_end - nread - bytes_left, mi->buf,
242			  nread + bytes_left);
243		  mi->buf = mi->buf_end - nread - bytes_left;
244		}
245
246	      eol = mi->buf + bytes_left + nread - 1;
247
248	      for (i = bytes_left; i < bytes_left + nread; ++i)
249		if (mi->buf[i] == '\n')
250		  {
251		    eol = mi->buf + i;
252		    break;
253		  }
254	    }
255	  cp = mi->buf;
256	  mi->buf = eol + 1;
257	  *eol = '\0';
258	}
259      else
260	{
261	  /* maps_init() wasn't able to allocate a buffer; do it the
262	     slow way.  */
263	  lseek (mi->fd, mi->offset, SEEK_SET);
264
265	  if ((nread = read (mi->fd, line, to_read)) <= 0)
266	    return 0;
267	  for (i = 0; i < nread && line[i] != '\n'; ++i)
268	    /* skip */;
269	  if (i < nread)
270	    {
271	      line[i] = '\0';
272	      mi->offset += i + 1;
273	    }
274	  else
275	    {
276	      if (to_read < sizeof (line))
277		to_read = sizeof (line) - 1;
278	      else
279		mi->offset += nread;	/* not supposed to happen... */
280	      continue;	/* duh, no newline found */
281	    }
282	  cp = line;
283	}
284
285      /* scan: "LOW-HIGH PERM OFFSET MAJOR:MINOR INUM PATH" */
286      cp = scan_hex (cp, low);
287      cp = scan_char (cp, &dash);
288      cp = scan_hex (cp, high);
289      cp = scan_string (cp, perm, sizeof (perm));
290      cp = scan_hex (cp, offset);
291      cp = scan_hex (cp, &major);
292      cp = scan_char (cp, &colon);
293      cp = scan_hex (cp, &minor);
294      cp = scan_dec (cp, &inum);
295      cp = scan_string (cp, path, path_size);
296      if (!cp || dash != '-' || colon != ':')
297	continue;	/* skip line with unknown or bad format */
298      return 1;
299    }
300  return 0;
301}
302
303static inline void
304maps_close (struct map_iterator *mi)
305{
306  if (mi->fd < 0)
307    return;
308  close (mi->fd);
309  mi->fd = -1;
310  if (mi->buf)
311    {
312      munmap (mi->buf_end - mi->buf_size, mi->buf_size);
313      mi->buf = mi->buf_end = 0;
314    }
315}
316
317#endif /* os_linux_h */
318