xref: /titanic_52/usr/src/lib/libtnfctl/util.c (revision e1c679fa4b0ab8c4bcaa6263974ca0c46e5b027f)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License, Version 1.0 only
6  * (the "License").  You may not use this file except in compliance
7  * with the License.
8  *
9  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10  * or http://www.opensolaris.org/os/licensing.
11  * See the License for the specific language governing permissions
12  * and limitations under the License.
13  *
14  * When distributing Covered Code, include this CDDL HEADER in each
15  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16  * If applicable, add the following below this CDDL HEADER, with the
17  * fields enclosed by brackets "[]" replaced with your own identifying
18  * information: Portions Copyright [yyyy] [name of copyright owner]
19  *
20  * CDDL HEADER END
21  */
22 /*
23  * Copyright (c) 1994, by Sun Microsytems, Inc.
24  */
25 
26 #pragma ident	"%Z%%M%	%I%	%E% SMI"
27 
28 /*
29  * Utility functions to initialize tnfctl handle, find functions that
30  * can be plugged into probes, find trace file information, and create
31  * a trace file for process tracing.
32  */
33 
34 #ifndef DEBUG
35 #define	NDEBUG	1
36 #endif
37 
38 #include "tnfctl_int.h"
39 #include "dbg.h"
40 
41 #include <assert.h>
42 #include <errno.h>
43 #include <stdio.h>
44 #include <string.h>
45 #include <unistd.h>
46 #include <stdlib.h>
47 #include <fcntl.h>
48 #include <sys/param.h>
49 
50 #include "tnf_buf.h"
51 /*
52  * Defines - Project private interfaces in libtnfprobe.so
53  */
54 
55 #define	TRACEFILE_NAME		"tnf_trace_file_name"
56 #define	TRACEFILE_SIZE		"tnf_trace_file_size"
57 #define	TRACEFILE_MIN		"tnf_trace_file_min"
58 #define	TRACE_ERROR		"_tnfw_b_control"
59 
60 #define	TRACE_ALLOC		"tnf_trace_alloc"
61 #define	TRACE_COMMIT		"tnf_trace_commit"
62 #define	TRACE_ROLLBACK		"tnf_trace_rollback"
63 #define	DEBUG_ENTRY		"tnf_probe_debug"
64 
65 #define	PROBE_LIST_HEAD		"__tnf_probe_list_head"
66 #define	PROBE_LIST_VALID	"__tnf_probe_list_valid"
67 
68 #define	NONTHREAD_TEST		"tnf_non_threaded_test_addr"
69 #define	THREAD_TEST		"tnf_threaded_test_addr"
70 #define	PROBE_THR_SYNC		"__tnf_probe_thr_sync"
71 
72 #define	MEMSEG_PTR		"__tnf_probe_memseg_p"
73 
74 /* Project private interfaces in libthread.so */
75 #define	LIBTHREAD_PRESENT	"thr_probe_getfunc_addr"
76 
77 /*
78  * Local declarations
79  */
80 
81 static tnfctl_errcode_t find_test_func(tnfctl_handle_t *hndl);
82 static tnfctl_errcode_t find_target_syms(tnfctl_handle_t *hndl);
83 static tnfctl_errcode_t find_trace_file_info(tnfctl_handle_t *hndl);
84 static tnfctl_errcode_t check_trace_error(tnfctl_handle_t *hndl);
85 
86 /*
87  * _tnfctl_refresh_process() - search for new shared objects.  If any
88  * found, discover probes in new shared objects.
89  *	NOT to be called in kernel mode.
90  */
91 
92 tnfctl_errcode_t
93 _tnfctl_refresh_process(tnfctl_handle_t *hndl, boolean_t *lmap_ok,
94 			enum event_op_t *dl_evt)
95 {
96 	tnfctl_errcode_t	prexstat = TNFCTL_ERR_NONE;
97 	boolean_t	release_lock;
98 
99 	assert(hndl->mode != KERNEL_MODE);
100 
101 	/*LINTED statement has no consequent: else*/
102 	LOCK(hndl, prexstat, release_lock);
103 
104 	prexstat = check_trace_error(hndl);
105 	if (prexstat)
106 		goto finish_func;
107 
108 	/*
109 	 * update the link map. caller decides what to do on
110 	 * inconsistent link map
111 	 */
112 	prexstat = _tnfctl_lmap_update(hndl, lmap_ok, dl_evt);
113 	if (prexstat)
114 		goto finish_func;
115 
116 	/* link map is ok now */
117 	prexstat = find_test_func(hndl);
118 	if (prexstat)
119 		goto finish_func;
120 	if (*dl_evt != EVT_NONE) {
121 		prexstat = _tnfctl_find_all_probes(hndl);
122 		if (prexstat)
123 			goto finish_func;
124 	}
125 
126 finish_func:
127 	/*LINTED statement has no consequent: else*/
128 	UNLOCK(hndl, release_lock);
129 
130 	return (prexstat);
131 }
132 
133 /*
134  * initialize tnfctl handle for a new target
135  */
136 tnfctl_errcode_t
137 _tnfctl_set_state(tnfctl_handle_t *hndl)
138 {
139 	tnfctl_errcode_t	prexstat = TNFCTL_ERR_NONE;
140 	boolean_t	lmap_ok;
141 	enum event_op_t	dl_evt;
142 	boolean_t	release_lock;
143 
144 	hndl->targ_pid = hndl->p_getpid(hndl->proc_p);
145 
146 	/*LINTED statement has no consequent: else*/
147 	LOCK(hndl, prexstat, release_lock);
148 
149 	/*
150 	 * initialize the link map table. If link map is not ok, it is an
151 	 * error.
152 	 */
153 	prexstat = _tnfctl_lmap_update(hndl, &lmap_ok, &dl_evt);
154 	if (prexstat)
155 		goto end_func;
156 
157 	/* find the needed target symbols */
158 	prexstat = find_target_syms(hndl);
159 	if (prexstat) {
160 		/* is libtnfprobe.so loaded in target ? */
161 		goto end_func;
162 	}
163 
164 	prexstat = find_trace_file_info(hndl);
165 	if (prexstat)
166 		goto end_func;
167 
168 	prexstat = find_test_func(hndl);
169 	if (prexstat)
170 		goto end_func;
171 
172 	prexstat = _tnfctl_find_all_probes(hndl);
173 	if (prexstat)
174 		goto end_func;
175 
176 	prexstat = check_trace_error(hndl);
177 	/* fall into end_func */
178 
179 end_func:
180 	/*LINTED statement has no consequent: else*/
181 	UNLOCK(hndl, release_lock);
182 
183 	return (prexstat);
184 }
185 
186 /*
187  * find the test function for a probe.  The test function could change
188  * with time, so we have to repeatedly check for the test function to use
189  */
190 static tnfctl_errcode_t
191 find_test_func(tnfctl_handle_t *hndl)
192 {
193 	long		thr_sync;
194 	int		miscstat;
195 
196 	if (hndl->mt_target == B_FALSE) {
197 		/* no libthread linked in */
198 		hndl->testfunc = hndl->nonthread_test;
199 	} else {
200 		/*
201 		 * check whether libthread/libtnfw have synced up.
202 		 * If not yet synced up, use non-threaded test function
203 		 */
204 
205 		/* assume we are going to use threaded test */
206 		hndl->testfunc = hndl->thread_test;
207 		miscstat = hndl->p_read(hndl->proc_p, hndl->thread_sync,
208 			&thr_sync, sizeof (thr_sync));
209 		if (miscstat != 0)
210 			return (TNFCTL_ERR_INTERNAL);
211 		/* if not yet synced up, change test func to non-threaded one */
212 		if (thr_sync == 0) {
213 			hndl->testfunc = hndl->nonthread_test;
214 		}
215 	}
216 
217 	/*
218 	 * Note: the testfunc in the target can change underneath us because
219 	 * in an MT program the init section of libthread changes all the
220 	 * test functions from the non-threaded one to the threaded one.
221 	 * So, every time we write out a probe, we have to make sure that
222 	 * we are using the correct test function by not trusting the test
223 	 * function in our copy of the probe.  A more fool-proof solution
224 	 * which will allow other fields in the probe to change internally
225 	 * is to refresh every probe on a _tnfctl_refresh_process()
226 	 */
227 	return (TNFCTL_ERR_NONE);
228 }
229 
230 /*
231  * check_trace_error() - checks whether there was an error in tracing
232  *	side effects trace_buf_state and trace_state in hndl
233  *	note: call this function only after trace_file_name is set up
234  *	in hndl
235  */
236 tnfctl_errcode_t
237 check_trace_error(tnfctl_handle_t *hndl)
238 {
239 	int		miscstat;
240 	uintptr_t	trace_error_ptr;
241 	TNFW_B_CONTROL	trace_error_rec;
242 
243 	/* read in the value of the control structure pointer */
244 	miscstat = hndl->p_read(hndl->proc_p, hndl->trace_error,
245 		&trace_error_ptr, sizeof (trace_error_ptr));
246 	if (miscstat != 0)
247 		return (TNFCTL_ERR_INTERNAL);
248 
249 	/* read in the value of the control structure */
250 	miscstat = hndl->p_read(hndl->proc_p, trace_error_ptr, &trace_error_rec,
251 		sizeof (trace_error_rec));
252 	if (miscstat != 0)
253 		return (TNFCTL_ERR_INTERNAL);
254 
255 	if (trace_error_rec.tnf_state == TNFW_B_NOBUFFER) {
256 		/*
257 		 * massage into correct state for caller - the target might
258 		 * not have hit the first probe and hence we got "no buffer".
259 		 * So, if the user had given a file name, return BUF_OK.
260 		 */
261 		if (hndl->trace_file_name == NULL)
262 			hndl->trace_buf_state = TNFCTL_BUF_NONE;
263 		else
264 			hndl->trace_buf_state = TNFCTL_BUF_OK;
265 	} else if (trace_error_rec.tnf_state == TNFW_B_BROKEN) {
266 		hndl->trace_buf_state = TNFCTL_BUF_BROKEN;
267 	} else {
268 		hndl->trace_buf_state = TNFCTL_BUF_OK;
269 	}
270 
271 	if (TNFW_B_IS_STOPPED(trace_error_rec.tnf_state))
272 		hndl->trace_state = B_FALSE;
273 	else
274 		hndl->trace_state = B_TRUE;
275 
276 	return (TNFCTL_ERR_NONE);
277 
278 }				/* end find_alloc_func */
279 
280 /*
281  * find_target_syms() - finds needed target functions
282  * 	sideffects allocfunc, commitfunc, endfunc, rollbackfunc in hndl
283  */
284 static tnfctl_errcode_t
285 find_target_syms(tnfctl_handle_t *hndl)
286 {
287 	tnfctl_errcode_t	prexstat;
288 	uintptr_t		temp_addr;
289 	int			miscstat;
290 
291 	prexstat = _tnfctl_sym_find(hndl, TRACE_ALLOC, &hndl->allocfunc);
292 	if (prexstat)
293 		goto end_of_func;
294 
295 	prexstat = _tnfctl_sym_find(hndl, TRACE_COMMIT, &hndl->commitfunc);
296 	if (prexstat)
297 		goto end_of_func;
298 
299 	prexstat = _tnfctl_sym_find(hndl, TRACE_END_FUNC, &hndl->endfunc);
300 	if (prexstat)
301 		goto end_of_func;
302 
303 	prexstat = _tnfctl_sym_find(hndl, TRACE_ROLLBACK, &hndl->rollbackfunc);
304 	if (prexstat)
305 		goto end_of_func;
306 
307 	prexstat = _tnfctl_sym_find(hndl, PROBE_LIST_HEAD,
308 					&hndl->probelist_head);
309 	if (prexstat)
310 		goto end_of_func;
311 
312 	prexstat = _tnfctl_sym_find(hndl, TRACE_ERROR, &hndl->trace_error);
313 	if (prexstat)
314 		goto end_of_func;
315 
316 	prexstat = _tnfctl_sym_find(hndl, MEMSEG_PTR, &temp_addr);
317 	if (prexstat)
318 		goto end_of_func;
319 
320 	/* dereference to get the actual address of structure */
321 	miscstat = hndl->p_read(hndl->proc_p, temp_addr, &hndl->memseg_p,
322 			sizeof (hndl->memseg_p));
323 	if (miscstat != 0)
324 		return (TNFCTL_ERR_INTERNAL);
325 
326 	prexstat = _tnfctl_sym_find(hndl, PROBE_LIST_VALID,
327 					&hndl->probelist_valid);
328 	if (prexstat)
329 		goto end_of_func;
330 
331 	prexstat = _tnfctl_sym_find(hndl, NONTHREAD_TEST, &temp_addr);
332 	if (prexstat)
333 		goto end_of_func;
334 
335 	/* dereference to get the actual function address */
336 	miscstat = hndl->p_read(hndl->proc_p, temp_addr, &hndl->nonthread_test,
337 			sizeof (hndl->nonthread_test));
338 	if (miscstat != 0)
339 		return (TNFCTL_ERR_INTERNAL);
340 
341 	prexstat = _tnfctl_sym_find(hndl, THREAD_TEST, &temp_addr);
342 	if (prexstat)
343 		goto end_of_func;
344 
345 	/* dereference to get the actual function address */
346 	miscstat = hndl->p_read(hndl->proc_p, temp_addr, &hndl->thread_test,
347 			sizeof (hndl->thread_test));
348 	if (miscstat != 0)
349 		return (TNFCTL_ERR_INTERNAL);
350 
351 	prexstat = _tnfctl_sym_find(hndl, PROBE_THR_SYNC, &hndl->thread_sync);
352 	if (prexstat)
353 		goto end_of_func;
354 
355 	prexstat = _tnfctl_sym_find(hndl, LIBTHREAD_PRESENT, &temp_addr);
356 	if (prexstat) {
357 		if (prexstat == TNFCTL_ERR_BADARG) {
358 			/* no libthread linked in */
359 			hndl->mt_target = B_FALSE;
360 			/* this is not an error condition */
361 			prexstat = TNFCTL_ERR_NONE;
362 		} else {
363 			return (prexstat);
364 		}
365 	} else {
366 		hndl->mt_target = B_TRUE;
367 	}
368 
369 end_of_func:
370 	if (prexstat == TNFCTL_ERR_BADARG)
371 		prexstat = TNFCTL_ERR_NOLIBTNFPROBE;
372 
373 	return (prexstat);
374 }
375 
376 /*
377  * _tnfctl_create_tracefile() - initializes tracefile, sets the tracefile name
378  *	and size
379  *	side effects trace_file_name and trace_buf_size in hndl
380  */
381 
382 #define	ZBUFSZ		(64 * 1024)
383 
384 tnfctl_errcode_t
385 _tnfctl_create_tracefile(tnfctl_handle_t *hndl, const char *trace_file_name,
386 			uint_t trace_file_size)
387 {
388 	char		*preexisting;
389 	tnfctl_errcode_t	prexstat;
390 	int		miscstat;
391 	char		path[MAXPATHLEN];
392 	uintptr_t	name_addr, size_addr;
393 	uint_t		outsize;
394 	char		zerobuf[ZBUFSZ];
395 	int		fd, sz, i;
396 
397 	/* find the neccessary symbols in the target */
398 	prexstat = _tnfctl_sym_find(hndl, TRACEFILE_NAME, &name_addr);
399 	if (prexstat) {
400 		if (prexstat == TNFCTL_ERR_BADARG)
401 			prexstat = TNFCTL_ERR_INTERNAL;
402 		return (prexstat);
403 	}
404 	prexstat = _tnfctl_sym_find(hndl, TRACEFILE_SIZE, &size_addr);
405 	if (prexstat) {
406 		if (prexstat == TNFCTL_ERR_BADARG)
407 			prexstat = TNFCTL_ERR_INTERNAL;
408 		return (prexstat);
409 	}
410 
411 	/* Double check that a file name doesn't already exist */
412 	preexisting = NULL;
413 	prexstat = _tnfctl_readstr_targ(hndl, name_addr, &preexisting);
414 	if (prexstat) {
415 		if (preexisting)
416 			free(preexisting);
417 		return (prexstat);
418 	}
419 
420 	/* There better not be a file name there yet */
421 	assert(preexisting[0] == '\0');
422 
423 	/* paranoia - for optimized compilation */
424 	if (preexisting[0] != '\0')
425 		return (TNFCTL_ERR_BUFEXISTS);
426 
427 	/* free memory in preexisting string */
428 	if (preexisting)
429 		free(preexisting);
430 
431 	if (trace_file_size < hndl->trace_min_size) {
432 		return (TNFCTL_ERR_SIZETOOSMALL);
433 	}
434 
435 	/* do we have an absolute, relative or no pathname specified? */
436 	if (trace_file_name == NULL) {
437 		return (TNFCTL_ERR_BADARG);
438 	}
439 	if (trace_file_name[0] == '/') {
440 		/* absolute path to tracefile specified */
441 		if ((strlen(trace_file_name) + 1) > (size_t) MAXPATHLEN) {
442 			/* directory specification too long */
443 			return (TNFCTL_ERR_BADARG);
444 		}
445 		(void) strcpy(path, trace_file_name);
446 	} else {
447 		char		   *cwd;
448 
449 		/* relative path to tracefile specified */
450 		cwd = getcwd(NULL, MAXPATHLEN);
451 		if (!cwd) {
452 			return (tnfctl_status_map(errno));
453 		}
454 		if ((strlen(cwd) + 1 + strlen(trace_file_name) + 1) >
455 			(size_t) MAXPATHLEN) {
456 			/* path name too long */
457 			return (TNFCTL_ERR_BADARG);
458 		}
459 		(void) sprintf(path, "%s/%s", cwd, trace_file_name);
460 
461 		free(cwd);
462 	}
463 
464 	outsize = trace_file_size;
465 
466 	DBG_TNF_PROBE_2(_tnfctl_create_tracefile_1, "libtnfctl",
467 		"sunw%verbosity 1; sunw%debug 'setting trace file name'",
468 		tnf_string, tracefile_name, path,
469 		tnf_long, tracefile_size, outsize);
470 
471 	/* unlink a previous tracefile (if one exists) */
472 	(void) unlink(path);
473 
474 	/* create the new tracefile */
475 	fd = open(path, O_CREAT | O_RDWR | O_TRUNC, 0644);
476 	if (fd < 0)	{
477 		return (tnfctl_status_map(errno));
478 	}
479 
480 	/* zero fill the file */
481 	(void) memset(zerobuf, 0, ZBUFSZ);
482 	sz = ZBUFSZ;
483 	for (i = 0; i < outsize; i += sz) {
484 		ulong_t		retval;
485 
486 		sz = ((outsize - i) > ZBUFSZ) ? ZBUFSZ : (outsize - i);
487 		retval = write(fd, zerobuf, sz);
488 		if (retval != sz) {
489 			/* trouble zeroing tracefile */
490 			return (tnfctl_status_map(errno));
491 		}
492 	}
493 
494 	/* close the file */
495 	(void) close(fd);
496 
497 	/* write the tracefile name and size into the target process */
498 	miscstat = hndl->p_write(hndl->proc_p, name_addr, path,
499 					strlen(path) + 1);
500 	if (miscstat != 0)
501 		return (TNFCTL_ERR_INTERNAL);
502 	miscstat = hndl->p_write(hndl->proc_p, size_addr, &outsize,
503 					sizeof (outsize));
504 	if (miscstat != 0)
505 		return (TNFCTL_ERR_INTERNAL);
506 
507 	hndl->trace_file_name = strdup(path);
508 	if (hndl->trace_file_name == NULL)
509 		return (TNFCTL_ERR_ALLOCFAIL);
510 	hndl->trace_buf_size = outsize;
511 	hndl->trace_buf_state = TNFCTL_BUF_OK;
512 	return (TNFCTL_ERR_NONE);
513 }				/* end _tnfctl_create_tracefile */
514 
515 /*
516  * find_trace_file_info()
517  *	finds out information about the trace file.
518  *	side effects trace_buf_size, trace_min_size, trace_file_name in hndl
519  */
520 
521 static tnfctl_errcode_t
522 find_trace_file_info(tnfctl_handle_t *hndl)
523 {
524 	tnfctl_errcode_t	prexstat;
525 	int		miscstat;
526 	char		*preexisting;
527 	uintptr_t	name_addr, size_addr, min_addr;
528 	uint_t		outsize, minoutsize;
529 
530 	/* find the neccessary symbols in the target */
531 	prexstat = _tnfctl_sym_find(hndl, TRACEFILE_NAME, &name_addr);
532 	if (prexstat) {
533 		if (prexstat == TNFCTL_ERR_BADARG)
534 			prexstat = TNFCTL_ERR_INTERNAL;
535 		return (prexstat);
536 	}
537 	prexstat = _tnfctl_sym_find(hndl, TRACEFILE_SIZE, &size_addr);
538 	if (prexstat) {
539 		if (prexstat == TNFCTL_ERR_BADARG)
540 			prexstat = TNFCTL_ERR_INTERNAL;
541 		return (prexstat);
542 	}
543 	prexstat = _tnfctl_sym_find(hndl, TRACEFILE_MIN, &min_addr);
544 	if (prexstat) {
545 		if (prexstat == TNFCTL_ERR_BADARG)
546 			prexstat = TNFCTL_ERR_INTERNAL;
547 		return (prexstat);
548 	}
549 
550 	/* read file name */
551 	preexisting = NULL;
552 	prexstat = _tnfctl_readstr_targ(hndl, name_addr, &preexisting);
553 	if (prexstat) {
554 		if (preexisting)
555 			free(preexisting);
556 		return (prexstat);
557 	}
558 
559 	/* read the minimum file size from the target */
560 	miscstat = hndl->p_read(hndl->proc_p, min_addr, &minoutsize,
561 							sizeof (minoutsize));
562 	if (miscstat != 0)
563 		return (TNFCTL_ERR_INTERNAL);
564 	hndl->trace_min_size = minoutsize;
565 
566 	/* if there is no filename, we are done */
567 	if (preexisting[0] == '\0') {
568 		hndl->trace_file_name = NULL;
569 		hndl->trace_buf_size = 0;
570 	} else {
571 		hndl->trace_file_name = preexisting;
572 		/* read size of file */
573 		miscstat = hndl->p_read(hndl->proc_p, size_addr,
574 				&outsize, sizeof (outsize));
575 		if (miscstat != 0)
576 			return (TNFCTL_ERR_INTERNAL);
577 		hndl->trace_buf_size = outsize;
578 	}
579 
580 	return (TNFCTL_ERR_NONE);
581 }				/* end find_trace_file_info */
582 
583 /*
584  * wrapper functions over native /proc functions implemented by proc
585  * layer
586  */
587 int
588 _tnfctl_read_targ(void *proc_p, uintptr_t addr, void *buf, size_t size)
589 {
590 	return (prb_proc_read(proc_p, addr, buf, size));
591 }
592 
593 int
594 _tnfctl_write_targ(void *proc_p, uintptr_t addr, void *buf, size_t size)
595 {
596 	return (prb_proc_write(proc_p, addr, buf, size));
597 }
598 
599 int
600 _tnfctl_loadobj_iter(void *proc_p, tnfctl_ind_obj_f *func, void *client_data)
601 {
602 	prb_loadobj_f *same_func = (prb_loadobj_f *) func;
603 
604 	return (prb_loadobj_iter(proc_p, same_func, client_data));
605 }
606 
607 pid_t
608 _tnfctl_pid_get(void *proc_p)
609 {
610 	return (prb_proc_pid_get(proc_p));
611 }
612 
613 /*
614  * _tnfctl_readstr_targ() - dereferences a string in the target
615  * 	NOTE: There is a similar routine called prb_proc_readstr()
616  *	      used by proc layer.  It would be better if there was only
617  *	      one of these functions defined.
618  */
619 
620 #define	BUFSZ	256
621 
622 tnfctl_errcode_t
623 _tnfctl_readstr_targ(tnfctl_handle_t *hndl, uintptr_t addr, char **outstr_pp)
624 {
625 	int		retstat;
626 	int		bufsz = BUFSZ;
627 	char		buffer[BUFSZ + 1];
628 	offset_t	offset;
629 	char		*ptr, *orig_ptr;
630 
631 	*outstr_pp = NULL;
632 	offset = 0;
633 
634 	/* allocate an inital return buffer */
635 	ptr = (char *) malloc(BUFSZ);
636 	if (!ptr) {
637 		DBG((void) fprintf(stderr,
638 			"_tnfctl_readstr_targ: malloc failed\n"));
639 		return (TNFCTL_ERR_ALLOCFAIL);
640 	}
641 	/*LINTED constant in conditional context*/
642 	while (1) {
643 		int			 i;
644 
645 		/* read a chunk into our buffer */
646 		retstat = hndl->p_read(hndl->proc_p, addr + offset, buffer,
647 								bufsz);
648 		if (retstat != 0) {
649 
650 			/*
651 			 * if we get into trouble with a large read, try again
652 			 * with a single byte.  Subsequent failiure is real ...
653 			 */
654 			if (bufsz > 1) {
655 				bufsz = 1;
656 				continue;
657 			}
658 
659 			DBG((void) fprintf(stderr,
660 			    "_tnfctl_readstr_targ: target read failed: \n"));
661 			free(ptr);
662 			return (TNFCTL_ERR_INTERNAL);
663 		}
664 		/* copy the chracters into the return buffer */
665 		for (i = 0; i < bufsz; i++) {
666 			char			c = buffer[i];
667 
668 			ptr[offset + i] = c;
669 			if (c == '\0') {
670 				/* hooray! we saw the end of the string */
671 				*outstr_pp = ptr;
672 				return (TNFCTL_ERR_NONE);
673 			}
674 		}
675 
676 		/* bummer, need to grab another bufsz characters */
677 		offset += bufsz;
678 		orig_ptr = ptr;
679 		ptr = (char *) realloc(ptr, offset + bufsz);
680 		if (!ptr) {
681 			free(orig_ptr);
682 			DBG((void) fprintf(stderr,
683 				"_tnfctl_readstr_targ: realloc failed\n"));
684 			return (TNFCTL_ERR_ALLOCFAIL);
685 		}
686 	}
687 
688 #if defined(lint)
689 	return (TNFCTL_ERR_NONE);
690 #endif
691 
692 }
693