os-linux.h revision 6c61288b9c5b3c967937d9ab12113bc7e368e433
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
237	      eol = mi->buf + bytes_left + nread - 1;
238
239	      for (i = bytes_left; i < bytes_left + nread; ++i)
240		if (mi->buf[i] == '\n')
241		  {
242		    eol = mi->buf + i;
243		    break;
244		  }
245	    }
246	  cp = mi->buf;
247	  mi->buf = eol + 1;
248	  *eol = '\0';
249	}
250      else
251	{
252	  /* maps_init() wasn't able to allocate a buffer; do it the
253	     slow way.  */
254	  lseek (mi->fd, mi->offset, SEEK_SET);
255
256	  if ((nread = read (mi->fd, line, to_read)) <= 0)
257	    return 0;
258	  for (i = 0; i < nread && line[i] != '\n'; ++i)
259	    /* skip */;
260	  if (i < nread)
261	    {
262	      line[i] = '\0';
263	      mi->offset += i + 1;
264	    }
265	  else
266	    {
267	      if (to_read < sizeof (line))
268		to_read = sizeof (line) - 1;
269	      else
270		mi->offset += nread;	/* not supposed to happen... */
271	      continue;	/* duh, no newline found */
272	    }
273	  cp = line;
274	}
275
276      /* scan: "LOW-HIGH PERM OFFSET MAJOR:MINOR INUM PATH" */
277      cp = scan_hex (cp, low);
278      cp = scan_char (cp, &dash);
279      cp = scan_hex (cp, high);
280      cp = scan_string (cp, perm, sizeof (perm));
281      cp = scan_hex (cp, offset);
282      cp = scan_hex (cp, &major);
283      cp = scan_char (cp, &colon);
284      cp = scan_hex (cp, &minor);
285      cp = scan_dec (cp, &inum);
286      cp = scan_string (cp, path, path_size);
287      if (!cp || dash != '-' || colon != ':')
288	continue;	/* skip line with unknown or bad format */
289      return 1;
290    }
291  return 0;
292}
293
294static inline void
295maps_close (struct map_iterator *mi)
296{
297  if (mi->fd < 0)
298    return;
299  close (mi->fd);
300  mi->fd = -1;
301  if (mi->buf)
302    {
303      munmap (mi->buf_end - mi->buf_size, mi->buf_size);
304      mi->buf = mi->buf_end = 0;
305    }
306}
307
308#endif /* os_linux_h */
309