Commit | Line | Data |
---|---|---|
c906108c SS |
1 | /* Acorn Risc Machine host machine support. |
2 | Copyright (C) 1988, 1989, 1991 Free Software Foundation, Inc. | |
3 | ||
c5aa993b | 4 | This file is part of GDB. |
c906108c | 5 | |
c5aa993b JM |
6 | This program is free software; you can redistribute it and/or modify |
7 | it under the terms of the GNU General Public License as published by | |
8 | the Free Software Foundation; either version 2 of the License, or | |
9 | (at your option) any later version. | |
c906108c | 10 | |
c5aa993b JM |
11 | This program is distributed in the hope that it will be useful, |
12 | but WITHOUT ANY WARRANTY; without even the implied warranty of | |
13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
14 | GNU General Public License for more details. | |
c906108c | 15 | |
c5aa993b JM |
16 | You should have received a copy of the GNU General Public License |
17 | along with this program; if not, write to the Free Software | |
18 | Foundation, Inc., 59 Temple Place - Suite 330, | |
19 | Boston, MA 02111-1307, USA. */ | |
c906108c SS |
20 | |
21 | #include "defs.h" | |
22 | #include "frame.h" | |
23 | #include "inferior.h" | |
24 | #include "arm-opcode.h" | |
25 | ||
26 | #include <sys/param.h> | |
27 | #include <sys/dir.h> | |
28 | #include <signal.h> | |
29 | #include <sys/ioctl.h> | |
30 | #include <sys/ptrace.h> | |
31 | #include <machine/reg.h> | |
32 | ||
33 | #define N_TXTADDR(hdr) 0x8000 | |
34 | #define N_DATADDR(hdr) (hdr.a_text + 0x8000) | |
35 | ||
36 | #include "gdbcore.h" | |
37 | ||
38 | #include <sys/user.h> /* After a.out.h */ | |
39 | #include <sys/file.h> | |
40 | #include "gdb_stat.h" | |
41 | ||
42 | #include <errno.h> | |
43 | ||
44 | void | |
45 | fetch_inferior_registers (regno) | |
c5aa993b | 46 | int regno; /* Original value discarded */ |
c906108c SS |
47 | { |
48 | register unsigned int regaddr; | |
49 | char buf[MAX_REGISTER_RAW_SIZE]; | |
50 | register int i; | |
51 | ||
52 | struct user u; | |
53 | unsigned int offset = (char *) &u.u_ar0 - (char *) &u; | |
54 | offset = ptrace (PT_READ_U, inferior_pid, (PTRACE_ARG3_TYPE) offset, 0) | |
c5aa993b | 55 | - KERNEL_U_ADDR; |
c906108c SS |
56 | |
57 | registers_fetched (); | |
c5aa993b | 58 | |
c906108c SS |
59 | for (regno = 0; regno < 16; regno++) |
60 | { | |
61 | regaddr = offset + regno * 4; | |
c5aa993b JM |
62 | *(int *) &buf[0] = ptrace (PT_READ_U, inferior_pid, |
63 | (PTRACE_ARG3_TYPE) regaddr, 0); | |
c906108c | 64 | if (regno == PC_REGNUM) |
c5aa993b | 65 | *(int *) &buf[0] = GET_PC_PART (*(int *) &buf[0]); |
c906108c SS |
66 | supply_register (regno, buf); |
67 | } | |
c5aa993b JM |
68 | *(int *) &buf[0] = ptrace (PT_READ_U, inferior_pid, |
69 | (PTRACE_ARG3_TYPE) (offset + PC * 4), 0); | |
70 | supply_register (PS_REGNUM, buf); /* set virtual register ps same as pc */ | |
c906108c SS |
71 | |
72 | /* read the floating point registers */ | |
c5aa993b JM |
73 | offset = (char *) &u.u_fp_regs - (char *) &u; |
74 | *(int *) buf = ptrace (PT_READ_U, inferior_pid, (PTRACE_ARG3_TYPE) offset, 0); | |
c906108c | 75 | supply_register (FPS_REGNUM, buf); |
c5aa993b JM |
76 | for (regno = 16; regno < 24; regno++) |
77 | { | |
c906108c | 78 | regaddr = offset + 4 + 12 * (regno - 16); |
c5aa993b JM |
79 | for (i = 0; i < 12; i += sizeof (int)) |
80 | *(int *) &buf[i] = ptrace (PT_READ_U, inferior_pid, | |
81 | (PTRACE_ARG3_TYPE) (regaddr + i), 0); | |
c906108c | 82 | supply_register (regno, buf); |
c5aa993b | 83 | } |
c906108c SS |
84 | } |
85 | ||
86 | /* Store our register values back into the inferior. | |
87 | If REGNO is -1, do this for all registers. | |
88 | Otherwise, REGNO specifies which register (so we can save time). */ | |
89 | ||
90 | void | |
91 | store_inferior_registers (regno) | |
92 | int regno; | |
93 | { | |
94 | register unsigned int regaddr; | |
95 | char buf[80]; | |
96 | ||
97 | struct user u; | |
98 | unsigned long value; | |
99 | unsigned int offset = (char *) &u.u_ar0 - (char *) &u; | |
100 | offset = ptrace (PT_READ_U, inferior_pid, (PTRACE_ARG3_TYPE) offset, 0) | |
c5aa993b | 101 | - KERNEL_U_ADDR; |
c906108c | 102 | |
c5aa993b JM |
103 | if (regno >= 0) |
104 | { | |
105 | if (regno >= 16) | |
106 | return; | |
c906108c SS |
107 | regaddr = offset + 4 * regno; |
108 | errno = 0; | |
c5aa993b | 109 | value = read_register (regno); |
c906108c | 110 | if (regno == PC_REGNUM) |
c5aa993b | 111 | value = SET_PC_PART (read_register (PS_REGNUM), value); |
c906108c SS |
112 | ptrace (PT_WRITE_U, inferior_pid, (PTRACE_ARG3_TYPE) regaddr, value); |
113 | if (errno != 0) | |
114 | { | |
115 | sprintf (buf, "writing register number %d", regno); | |
116 | perror_with_name (buf); | |
117 | } | |
118 | } | |
c5aa993b JM |
119 | else |
120 | for (regno = 0; regno < 15; regno++) | |
121 | { | |
122 | regaddr = offset + regno * 4; | |
123 | errno = 0; | |
124 | value = read_register (regno); | |
125 | if (regno == PC_REGNUM) | |
126 | value = SET_PC_PART (read_register (PS_REGNUM), value); | |
127 | ptrace (6, inferior_pid, (PTRACE_ARG3_TYPE) regaddr, value); | |
128 | if (errno != 0) | |
129 | { | |
130 | sprintf (buf, "writing all regs, number %d", regno); | |
131 | perror_with_name (buf); | |
132 | } | |
133 | } | |
c906108c SS |
134 | } |
135 | \f | |
136 | /* Work with core dump and executable files, for GDB. | |
137 | This code would be in corefile.c if it weren't machine-dependent. */ | |
138 | ||
139 | /* Structure to describe the chain of shared libraries used | |
140 | by the execfile. | |
141 | e.g. prog shares Xt which shares X11 which shares c. */ | |
142 | ||
c5aa993b JM |
143 | struct shared_library |
144 | { | |
145 | struct exec_header header; | |
146 | char name[SHLIBLEN]; | |
147 | CORE_ADDR text_start; /* CORE_ADDR of 1st byte of text, this file */ | |
148 | long data_offset; /* offset of data section in file */ | |
149 | int chan; /* file descriptor for the file */ | |
150 | struct shared_library *shares; /* library this one shares */ | |
c906108c SS |
151 | }; |
152 | static struct shared_library *shlib = 0; | |
153 | ||
154 | /* Hook for `exec_file_command' command to call. */ | |
155 | ||
156 | extern void (*exec_file_display_hook) (); | |
c5aa993b | 157 | |
c906108c SS |
158 | static CORE_ADDR unshared_text_start; |
159 | ||
160 | /* extended header from exec file (for shared library info) */ | |
161 | ||
162 | static struct exec_header exec_header; | |
163 | \f | |
164 | void | |
165 | core_file_command (filename, from_tty) | |
166 | char *filename; | |
167 | int from_tty; | |
168 | { | |
169 | int val; | |
c906108c SS |
170 | |
171 | /* Discard all vestiges of any previous core file | |
172 | and mark data and stack spaces as empty. */ | |
173 | ||
174 | if (corefile) | |
175 | free (corefile); | |
176 | corefile = 0; | |
177 | ||
178 | if (corechan >= 0) | |
179 | close (corechan); | |
180 | corechan = -1; | |
181 | ||
182 | data_start = 0; | |
183 | data_end = 0; | |
184 | stack_start = STACK_END_ADDR; | |
185 | stack_end = STACK_END_ADDR; | |
186 | ||
187 | /* Now, if a new core file was specified, open it and digest it. */ | |
188 | ||
189 | if (filename) | |
190 | { | |
191 | filename = tilde_expand (filename); | |
192 | make_cleanup (free, filename); | |
c5aa993b | 193 | |
c906108c SS |
194 | if (have_inferior_p ()) |
195 | error ("To look at a core file, you must kill the program with \"kill\"."); | |
196 | corechan = open (filename, O_RDONLY, 0); | |
197 | if (corechan < 0) | |
198 | perror_with_name (filename); | |
199 | /* 4.2-style (and perhaps also sysV-style) core dump file. */ | |
200 | { | |
201 | struct user u; | |
202 | ||
203 | unsigned int reg_offset, fp_reg_offset; | |
204 | ||
205 | val = myread (corechan, &u, sizeof u); | |
206 | if (val < 0) | |
207 | perror_with_name ("Not a core file: reading upage"); | |
208 | if (val != sizeof u) | |
209 | error ("Not a core file: could only read %d bytes", val); | |
210 | ||
211 | /* We are depending on exec_file_command having been called | |
212 | previously to set exec_data_start. Since the executable | |
213 | and the core file share the same text segment, the address | |
214 | of the data segment will be the same in both. */ | |
215 | data_start = exec_data_start; | |
216 | ||
217 | data_end = data_start + NBPG * u.u_dsize; | |
218 | stack_start = stack_end - NBPG * u.u_ssize; | |
219 | data_offset = NBPG * UPAGES; | |
220 | stack_offset = NBPG * (UPAGES + u.u_dsize); | |
221 | ||
222 | /* Some machines put an absolute address in here and some put | |
223 | the offset in the upage of the regs. */ | |
224 | reg_offset = (int) u.u_ar0; | |
225 | if (reg_offset > NBPG * UPAGES) | |
226 | reg_offset -= KERNEL_U_ADDR; | |
c5aa993b | 227 | fp_reg_offset = (char *) &u.u_fp_regs - (char *) &u; |
c906108c SS |
228 | |
229 | /* I don't know where to find this info. | |
230 | So, for now, mark it as not available. */ | |
231 | N_SET_MAGIC (core_aouthdr, 0); | |
232 | ||
233 | /* Read the register values out of the core file and store | |
234 | them where `read_register' will find them. */ | |
235 | ||
236 | { | |
237 | register int regno; | |
238 | ||
239 | for (regno = 0; regno < NUM_REGS; regno++) | |
240 | { | |
241 | char buf[MAX_REGISTER_RAW_SIZE]; | |
242 | ||
243 | if (regno < 16) | |
c5aa993b | 244 | val = lseek (corechan, reg_offset + 4 * regno, 0); |
c906108c | 245 | else if (regno < 24) |
c5aa993b | 246 | val = lseek (corechan, fp_reg_offset + 4 + 12 * (regno - 24), 0); |
c906108c | 247 | else if (regno == 24) |
c5aa993b | 248 | val = lseek (corechan, fp_reg_offset, 0); |
c906108c | 249 | else if (regno == 25) |
c5aa993b | 250 | val = lseek (corechan, reg_offset + 4 * PC, 0); |
c906108c SS |
251 | if (val < 0 |
252 | || (val = myread (corechan, buf, sizeof buf)) < 0) | |
253 | { | |
c5aa993b JM |
254 | char *buffer = (char *) alloca (strlen (REGISTER_NAME (regno)) |
255 | + 30); | |
c906108c SS |
256 | strcpy (buffer, "Reading register "); |
257 | strcat (buffer, REGISTER_NAME (regno)); | |
c5aa993b | 258 | |
c906108c SS |
259 | perror_with_name (buffer); |
260 | } | |
261 | ||
262 | if (regno == PC_REGNUM) | |
c5aa993b | 263 | *(int *) buf = GET_PC_PART (*(int *) buf); |
c906108c SS |
264 | supply_register (regno, buf); |
265 | } | |
266 | } | |
267 | } | |
268 | if (filename[0] == '/') | |
269 | corefile = savestring (filename, strlen (filename)); | |
270 | else | |
271 | { | |
272 | corefile = concat (current_directory, "/", filename, NULL); | |
273 | } | |
274 | ||
275 | flush_cached_frames (); | |
276 | select_frame (get_current_frame (), 0); | |
277 | validate_files (); | |
278 | } | |
279 | else if (from_tty) | |
280 | printf ("No core file now.\n"); | |
281 | } | |
282 | ||
283 | #if 0 | |
284 | /* Work with core dump and executable files, for GDB. | |
285 | This code would be in corefile.c if it weren't machine-dependent. */ | |
286 | ||
287 | /* Structure to describe the chain of shared libraries used | |
288 | by the execfile. | |
289 | e.g. prog shares Xt which shares X11 which shares c. */ | |
290 | ||
c5aa993b JM |
291 | struct shared_library |
292 | { | |
293 | struct exec_header header; | |
294 | char name[SHLIBLEN]; | |
295 | CORE_ADDR text_start; /* CORE_ADDR of 1st byte of text, this file */ | |
296 | long data_offset; /* offset of data section in file */ | |
297 | int chan; /* file descriptor for the file */ | |
298 | struct shared_library *shares; /* library this one shares */ | |
c906108c SS |
299 | }; |
300 | static struct shared_library *shlib = 0; | |
301 | ||
302 | /* Hook for `exec_file_command' command to call. */ | |
303 | ||
304 | extern void (*exec_file_display_hook) (); | |
c5aa993b | 305 | |
c906108c SS |
306 | static CORE_ADDR unshared_text_start; |
307 | ||
308 | /* extended header from exec file (for shared library info) */ | |
309 | ||
310 | static struct exec_header exec_header; | |
311 | ||
312 | void | |
313 | exec_file_command (filename, from_tty) | |
314 | char *filename; | |
315 | int from_tty; | |
316 | { | |
317 | int val; | |
318 | ||
319 | /* Eliminate all traces of old exec file. | |
320 | Mark text segment as empty. */ | |
321 | ||
322 | if (execfile) | |
323 | free (execfile); | |
324 | execfile = 0; | |
325 | data_start = 0; | |
326 | data_end -= exec_data_start; | |
327 | text_start = 0; | |
328 | unshared_text_start = 0; | |
329 | text_end = 0; | |
330 | exec_data_start = 0; | |
331 | exec_data_end = 0; | |
332 | if (execchan >= 0) | |
333 | close (execchan); | |
334 | execchan = -1; | |
c5aa993b JM |
335 | if (shlib) |
336 | { | |
337 | close_shared_library (shlib); | |
c906108c | 338 | shlib = 0; |
c5aa993b | 339 | } |
c906108c SS |
340 | |
341 | /* Now open and digest the file the user requested, if any. */ | |
342 | ||
343 | if (filename) | |
344 | { | |
345 | filename = tilde_expand (filename); | |
346 | make_cleanup (free, filename); | |
347 | ||
348 | execchan = openp (getenv ("PATH"), 1, filename, O_RDONLY, 0, | |
349 | &execfile); | |
350 | if (execchan < 0) | |
351 | perror_with_name (filename); | |
352 | ||
353 | { | |
354 | struct stat st_exec; | |
355 | ||
356 | #ifdef HEADER_SEEK_FD | |
357 | HEADER_SEEK_FD (execchan); | |
358 | #endif | |
c5aa993b | 359 | |
c906108c SS |
360 | val = myread (execchan, &exec_header, sizeof exec_header); |
361 | exec_aouthdr = exec_header.a_exec; | |
362 | ||
363 | if (val < 0) | |
364 | perror_with_name (filename); | |
365 | ||
366 | text_start = 0x8000; | |
367 | ||
368 | /* Look for shared library if needed */ | |
369 | if (exec_header.a_exec.a_magic & MF_USES_SL) | |
c5aa993b | 370 | shlib = open_shared_library (exec_header.a_shlibname, text_start); |
c906108c SS |
371 | |
372 | text_offset = N_TXTOFF (exec_aouthdr); | |
373 | exec_data_offset = N_TXTOFF (exec_aouthdr) + exec_aouthdr.a_text; | |
374 | ||
c5aa993b JM |
375 | if (shlib) |
376 | { | |
377 | unshared_text_start = shared_text_end (shlib) & ~0x7fff; | |
c906108c SS |
378 | stack_start = shlib->header.a_exec.a_sldatabase; |
379 | stack_end = STACK_END_ADDR; | |
c5aa993b JM |
380 | } |
381 | else | |
382 | unshared_text_start = 0x8000; | |
c906108c SS |
383 | text_end = unshared_text_start + exec_aouthdr.a_text; |
384 | ||
385 | exec_data_start = unshared_text_start + exec_aouthdr.a_text; | |
c5aa993b | 386 | exec_data_end = exec_data_start + exec_aouthdr.a_data; |
c906108c SS |
387 | |
388 | data_start = exec_data_start; | |
389 | data_end += exec_data_start; | |
390 | ||
391 | fstat (execchan, &st_exec); | |
392 | exec_mtime = st_exec.st_mtime; | |
393 | } | |
394 | ||
395 | validate_files (); | |
396 | } | |
397 | else if (from_tty) | |
398 | printf ("No executable file now.\n"); | |
399 | ||
400 | /* Tell display code (if any) about the changed file name. */ | |
401 | if (exec_file_display_hook) | |
402 | (*exec_file_display_hook) (filename); | |
403 | } | |
404 | #endif | |
405 | ||
406 | #if 0 | |
407 | /* Read from the program's memory (except for inferior processes). | |
408 | This function is misnamed, since it only reads, never writes; and | |
409 | since it will use the core file and/or executable file as necessary. | |
410 | ||
411 | It should be extended to write as well as read, FIXME, for patching files. | |
412 | ||
413 | Return 0 if address could be read, EIO if addresss out of bounds. */ | |
414 | ||
415 | int | |
416 | xfer_core_file (memaddr, myaddr, len) | |
417 | CORE_ADDR memaddr; | |
418 | char *myaddr; | |
419 | int len; | |
420 | { | |
421 | register int i; | |
422 | register int val; | |
423 | int xferchan; | |
424 | char **xferfile; | |
425 | int fileptr; | |
426 | int returnval = 0; | |
427 | ||
428 | while (len > 0) | |
429 | { | |
430 | xferfile = 0; | |
431 | xferchan = 0; | |
432 | ||
433 | /* Determine which file the next bunch of addresses reside in, | |
c5aa993b JM |
434 | and where in the file. Set the file's read/write pointer |
435 | to point at the proper place for the desired address | |
436 | and set xferfile and xferchan for the correct file. | |
c906108c | 437 | |
c5aa993b | 438 | If desired address is nonexistent, leave them zero. |
c906108c | 439 | |
c5aa993b JM |
440 | i is set to the number of bytes that can be handled |
441 | along with the next address. | |
c906108c | 442 | |
c5aa993b | 443 | We put the most likely tests first for efficiency. */ |
c906108c SS |
444 | |
445 | /* Note that if there is no core file | |
c5aa993b | 446 | data_start and data_end are equal. */ |
c906108c SS |
447 | if (memaddr >= data_start && memaddr < data_end) |
448 | { | |
449 | i = min (len, data_end - memaddr); | |
450 | fileptr = memaddr - data_start + data_offset; | |
451 | xferfile = &corefile; | |
452 | xferchan = corechan; | |
453 | } | |
454 | /* Note that if there is no core file | |
c5aa993b | 455 | stack_start and stack_end define the shared library data. */ |
c906108c SS |
456 | else if (memaddr >= stack_start && memaddr < stack_end) |
457 | { | |
c5aa993b JM |
458 | if (corechan < 0) |
459 | { | |
460 | struct shared_library *lib; | |
461 | for (lib = shlib; lib; lib = lib->shares) | |
462 | if (memaddr >= lib->header.a_exec.a_sldatabase && | |
463 | memaddr < lib->header.a_exec.a_sldatabase + | |
464 | lib->header.a_exec.a_data) | |
465 | break; | |
466 | if (lib) | |
467 | { | |
468 | i = min (len, lib->header.a_exec.a_sldatabase + | |
469 | lib->header.a_exec.a_data - memaddr); | |
470 | fileptr = lib->data_offset + memaddr - | |
471 | lib->header.a_exec.a_sldatabase; | |
472 | xferfile = execfile; | |
473 | xferchan = lib->chan; | |
c906108c | 474 | } |
c5aa993b JM |
475 | } |
476 | else | |
477 | { | |
478 | i = min (len, stack_end - memaddr); | |
479 | fileptr = memaddr - stack_start + stack_offset; | |
480 | xferfile = &corefile; | |
481 | xferchan = corechan; | |
c906108c SS |
482 | } |
483 | } | |
484 | else if (corechan < 0 | |
485 | && memaddr >= exec_data_start && memaddr < exec_data_end) | |
486 | { | |
487 | i = min (len, exec_data_end - memaddr); | |
488 | fileptr = memaddr - exec_data_start + exec_data_offset; | |
489 | xferfile = &execfile; | |
490 | xferchan = execchan; | |
491 | } | |
492 | else if (memaddr >= text_start && memaddr < text_end) | |
493 | { | |
c5aa993b JM |
494 | struct shared_library *lib; |
495 | for (lib = shlib; lib; lib = lib->shares) | |
496 | if (memaddr >= lib->text_start && | |
497 | memaddr < lib->text_start + lib->header.a_exec.a_text) | |
498 | break; | |
499 | if (lib) | |
500 | { | |
501 | i = min (len, lib->header.a_exec.a_text + | |
502 | lib->text_start - memaddr); | |
503 | fileptr = memaddr - lib->text_start + text_offset; | |
504 | xferfile = &execfile; | |
505 | xferchan = lib->chan; | |
506 | } | |
507 | else | |
508 | { | |
509 | i = min (len, text_end - memaddr); | |
510 | fileptr = memaddr - unshared_text_start + text_offset; | |
511 | xferfile = &execfile; | |
512 | xferchan = execchan; | |
c906108c SS |
513 | } |
514 | } | |
515 | else if (memaddr < text_start) | |
516 | { | |
517 | i = min (len, text_start - memaddr); | |
518 | } | |
519 | else if (memaddr >= text_end | |
c5aa993b | 520 | && memaddr < (corechan >= 0 ? data_start : exec_data_start)) |
c906108c SS |
521 | { |
522 | i = min (len, data_start - memaddr); | |
523 | } | |
524 | else if (corechan >= 0 | |
525 | && memaddr >= data_end && memaddr < stack_start) | |
526 | { | |
527 | i = min (len, stack_start - memaddr); | |
528 | } | |
529 | else if (corechan < 0 && memaddr >= exec_data_end) | |
530 | { | |
c5aa993b | 531 | i = min (len, -memaddr); |
c906108c SS |
532 | } |
533 | else if (memaddr >= stack_end && stack_end != 0) | |
534 | { | |
c5aa993b | 535 | i = min (len, -memaddr); |
c906108c SS |
536 | } |
537 | else | |
538 | { | |
539 | /* Address did not classify into one of the known ranges. | |
540 | This shouldn't happen; we catch the endpoints. */ | |
96baa820 | 541 | internal_error ("Bad case logic in xfer_core_file."); |
c906108c SS |
542 | } |
543 | ||
544 | /* Now we know which file to use. | |
c5aa993b | 545 | Set up its pointer and transfer the data. */ |
c906108c SS |
546 | if (xferfile) |
547 | { | |
548 | if (*xferfile == 0) | |
549 | if (xferfile == &execfile) | |
550 | error ("No program file to examine."); | |
551 | else | |
552 | error ("No core dump file or running program to examine."); | |
553 | val = lseek (xferchan, fileptr, 0); | |
554 | if (val < 0) | |
555 | perror_with_name (*xferfile); | |
556 | val = myread (xferchan, myaddr, i); | |
557 | if (val < 0) | |
558 | perror_with_name (*xferfile); | |
559 | } | |
560 | /* If this address is for nonexistent memory, | |
c5aa993b JM |
561 | read zeros if reading, or do nothing if writing. |
562 | Actually, we never right. */ | |
c906108c SS |
563 | else |
564 | { | |
565 | memset (myaddr, '\0', i); | |
566 | returnval = EIO; | |
567 | } | |
568 | ||
569 | memaddr += i; | |
570 | myaddr += i; | |
571 | len -= i; | |
572 | } | |
573 | return returnval; | |
574 | } | |
575 | #endif |