19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
20 * or visit www.oracle.com if you need additional information or have any |
20 * or visit www.oracle.com if you need additional information or have any |
21 * questions. |
21 * questions. |
22 * |
22 * |
23 */ |
23 */ |
24 #include <stdarg.h> |
|
25 #include <stdio.h> |
|
26 #include <stdlib.h> |
|
27 #include <string.h> |
|
28 #include <fcntl.h> |
|
29 #include <thread_db.h> |
|
30 #include "libproc_impl.h" |
24 #include "libproc_impl.h" |
31 |
25 |
32 static const char* alt_root = NULL; |
26 static const char* alt_root = NULL; |
33 static int alt_root_len = -1; |
27 static int alt_root_len = -1; |
34 |
28 |
35 #define SA_ALTROOT "SA_ALTROOT" |
29 #define SA_ALTROOT "SA_ALTROOT" |
36 |
30 |
|
31 off_t ltell(int fd) { |
|
32 return lseek(fd, 0, SEEK_CUR); |
|
33 } |
|
34 |
37 static void init_alt_root() { |
35 static void init_alt_root() { |
38 if (alt_root_len == -1) { |
36 if (alt_root_len == -1) { |
39 alt_root = getenv(SA_ALTROOT); |
37 alt_root = getenv(SA_ALTROOT); |
40 if (alt_root) { |
38 if (alt_root) { |
41 alt_root_len = strlen(alt_root); |
39 alt_root_len = strlen(alt_root); |
42 } else { |
40 } else { |
43 alt_root_len = 0; |
41 alt_root_len = 0; |
44 } |
42 } |
45 } |
43 } |
46 } |
44 } |
47 |
45 |
48 int pathmap_open(const char* name) { |
46 int pathmap_open(const char* name) { |
49 int fd; |
47 int fd; |
50 char alt_path[PATH_MAX + 1]; |
48 char alt_path[PATH_MAX + 1]; |
51 |
49 |
52 init_alt_root(); |
50 init_alt_root(); |
53 fd = open(name, O_RDONLY); |
51 |
54 if (fd >= 0) { |
52 if (alt_root_len > 0) { |
|
53 strcpy(alt_path, alt_root); |
|
54 strcat(alt_path, name); |
|
55 fd = open(alt_path, O_RDONLY); |
|
56 if (fd >= 0) { |
|
57 print_debug("path %s substituted for %s\n", alt_path, name); |
55 return fd; |
58 return fd; |
56 } |
59 } |
57 |
60 |
58 if (alt_root_len > 0) { |
61 if (strrchr(name, '/')) { |
59 strcpy(alt_path, alt_root); |
62 strcpy(alt_path, alt_root); |
60 strcat(alt_path, name); |
63 strcat(alt_path, strrchr(name, '/')); |
61 fd = open(alt_path, O_RDONLY); |
64 fd = open(alt_path, O_RDONLY); |
62 if (fd >= 0) { |
65 if (fd >= 0) { |
63 print_debug("path %s substituted for %s\n", alt_path, name); |
66 print_debug("path %s substituted for %s\n", alt_path, name); |
64 return fd; |
67 return fd; |
65 } |
68 } |
66 |
69 } |
67 if (strrchr(name, '/')) { |
70 } else { |
68 strcpy(alt_path, alt_root); |
71 fd = open(name, O_RDONLY); |
69 strcat(alt_path, strrchr(name, '/')); |
72 if (fd >= 0) { |
70 fd = open(alt_path, O_RDONLY); |
73 return fd; |
71 if (fd >= 0) { |
74 } |
72 print_debug("path %s substituted for %s\n", alt_path, name); |
75 } |
73 return fd; |
76 return -1; |
74 } |
|
75 } |
|
76 } |
|
77 |
|
78 return -1; |
|
79 } |
77 } |
80 |
78 |
81 static bool _libsaproc_debug; |
79 static bool _libsaproc_debug; |
82 |
80 |
83 void print_debug(const char* format,...) { |
81 void print_debug(const char* format,...) { |
84 if (_libsaproc_debug) { |
82 if (_libsaproc_debug) { |
85 va_list alist; |
83 va_list alist; |
86 |
84 |
87 va_start(alist, format); |
85 va_start(alist, format); |
88 fputs("libsaproc DEBUG: ", stderr); |
86 fputs("libsaproc DEBUG: ", stderr); |
89 vfprintf(stderr, format, alist); |
87 vfprintf(stderr, format, alist); |
90 va_end(alist); |
88 va_end(alist); |
91 } |
89 } |
92 } |
90 } |
93 |
91 |
94 void print_error(const char* format,...) { |
92 void print_error(const char* format,...) { |
95 va_list alist; |
93 va_list alist; |
96 va_start(alist, format); |
94 va_start(alist, format); |
98 vfprintf(stderr, format, alist); |
96 vfprintf(stderr, format, alist); |
99 va_end(alist); |
97 va_end(alist); |
100 } |
98 } |
101 |
99 |
102 bool is_debug() { |
100 bool is_debug() { |
103 return _libsaproc_debug; |
101 return _libsaproc_debug; |
104 } |
102 } |
|
103 |
|
104 #ifdef __APPLE__ |
|
105 // get arch offset in file |
|
106 bool get_arch_off(int fd, cpu_type_t cputype, off_t *offset) { |
|
107 struct fat_header fatheader; |
|
108 struct fat_arch fatarch; |
|
109 off_t img_start = 0; |
|
110 |
|
111 off_t pos = ltell(fd); |
|
112 if (read(fd, (void *)&fatheader, sizeof(struct fat_header)) != sizeof(struct fat_header)) { |
|
113 return false; |
|
114 } |
|
115 if (fatheader.magic == FAT_CIGAM) { |
|
116 int i; |
|
117 for (i = 0; i < ntohl(fatheader.nfat_arch); i++) { |
|
118 if (read(fd, (void *)&fatarch, sizeof(struct fat_arch)) != sizeof(struct fat_arch)) { |
|
119 return false; |
|
120 } |
|
121 if (ntohl(fatarch.cputype) == cputype) { |
|
122 print_debug("fat offset=%x\n", ntohl(fatarch.offset)); |
|
123 img_start = ntohl(fatarch.offset); |
|
124 break; |
|
125 } |
|
126 } |
|
127 if (img_start == 0) { |
|
128 return false; |
|
129 } |
|
130 } |
|
131 lseek(fd, pos, SEEK_SET); |
|
132 *offset = img_start; |
|
133 return true; |
|
134 } |
|
135 |
|
136 bool is_macho_file(int fd) { |
|
137 mach_header_64 fhdr; |
|
138 off_t x86_64_off; |
|
139 |
|
140 if (fd < 0) { |
|
141 print_debug("Invalid file handle passed to is_macho_file\n"); |
|
142 return false; |
|
143 } |
|
144 |
|
145 off_t pos = ltell(fd); |
|
146 // check fat header |
|
147 if (!get_arch_off(fd, CPU_TYPE_X86_64, &x86_64_off)) { |
|
148 print_debug("failed to get fat header\n"); |
|
149 return false; |
|
150 } |
|
151 lseek(fd, x86_64_off, SEEK_SET); |
|
152 if (read(fd, (void *)&fhdr, sizeof(mach_header_64)) != sizeof(mach_header_64)) { |
|
153 return false; |
|
154 } |
|
155 lseek(fd, pos, SEEK_SET); // restore |
|
156 print_debug("fhdr.magic %x\n", fhdr.magic); |
|
157 return (fhdr.magic == MH_MAGIC_64 || fhdr.magic == MH_CIGAM_64); |
|
158 } |
|
159 |
|
160 #endif //__APPLE__ |
105 |
161 |
106 // initialize libproc |
162 // initialize libproc |
107 bool init_libproc(bool debug) { |
163 bool init_libproc(bool debug) { |
108 // init debug mode |
|
109 _libsaproc_debug = debug; |
164 _libsaproc_debug = debug; |
110 |
165 #ifndef __APPLE__ |
111 // initialize the thread_db library |
166 // initialize the thread_db library |
112 if (td_init() != TD_OK) { |
167 if (td_init() != TD_OK) { |
113 print_debug("libthread_db's td_init failed\n"); |
168 print_debug("libthread_db's td_init failed\n"); |
114 return false; |
169 return false; |
115 } |
170 } |
116 |
171 #endif // __APPLE__ |
117 return true; |
172 return true; |
118 } |
173 } |
119 |
174 |
120 static void destroy_lib_info(struct ps_prochandle* ph) { |
175 void destroy_lib_info(struct ps_prochandle* ph) { |
121 lib_info* lib = ph->libs; |
176 lib_info* lib = ph->libs; |
122 while (lib) { |
177 while (lib) { |
123 lib_info *next = lib->next; |
178 lib_info* next = lib->next; |
124 if (lib->symtab) { |
179 if (lib->symtab) { |
125 destroy_symtab(lib->symtab); |
180 destroy_symtab(lib->symtab); |
126 } |
181 } |
127 free(lib); |
182 free(lib); |
128 lib = next; |
183 lib = next; |
129 } |
184 } |
130 } |
185 } |
131 |
186 |
132 static void destroy_thread_info(struct ps_prochandle* ph) { |
187 void destroy_thread_info(struct ps_prochandle* ph) { |
133 thread_info* thr = ph->threads; |
188 sa_thread_info* thr = ph->threads; |
134 while (thr) { |
189 while (thr) { |
135 thread_info *next = thr->next; |
190 sa_thread_info* n = thr->next; |
136 free(thr); |
191 free(thr); |
137 thr = next; |
192 thr = n; |
138 } |
193 } |
139 } |
194 } |
140 |
|
141 // ps_prochandle cleanup |
|
142 |
195 |
143 // ps_prochandle cleanup |
196 // ps_prochandle cleanup |
144 void Prelease(struct ps_prochandle* ph) { |
197 void Prelease(struct ps_prochandle* ph) { |
145 // do the "derived class" clean-up first |
198 // do the "derived class" clean-up first |
146 ph->ops->release(ph); |
199 ph->ops->release(ph); |
147 destroy_lib_info(ph); |
200 destroy_lib_info(ph); |
148 destroy_thread_info(ph); |
201 destroy_thread_info(ph); |
149 free(ph); |
202 free(ph); |
150 } |
203 } |
151 |
204 |
152 lib_info* add_lib_info(struct ps_prochandle* ph, const char* libname, uintptr_t base) { |
205 lib_info* add_lib_info(struct ps_prochandle* ph, const char* libname, uintptr_t base) { |
153 return add_lib_info_fd(ph, libname, -1, base); |
206 return add_lib_info_fd(ph, libname, -1, base); |
154 } |
207 } |
155 |
208 |
156 lib_info* add_lib_info_fd(struct ps_prochandle* ph, const char* libname, int fd, uintptr_t base) { |
209 lib_info* add_lib_info_fd(struct ps_prochandle* ph, const char* libname, int fd, uintptr_t base) { |
157 lib_info* newlib; |
210 lib_info* newlib; |
158 |
211 print_debug("add_lib_info_fd %s\n", libname); |
159 if ( (newlib = (lib_info*) calloc(1, sizeof(struct lib_info))) == NULL) { |
212 |
160 print_debug("can't allocate memory for lib_info\n"); |
213 if ( (newlib = (lib_info*) calloc(1, sizeof(struct lib_info))) == NULL) { |
161 return NULL; |
214 print_debug("can't allocate memory for lib_info\n"); |
162 } |
215 return NULL; |
163 |
216 } |
164 strncpy(newlib->name, libname, sizeof(newlib->name)); |
217 |
165 newlib->base = base; |
218 strncpy(newlib->name, libname, sizeof(newlib->name)); |
166 |
219 newlib->base = base; |
167 if (fd == -1) { |
220 |
168 if ( (newlib->fd = pathmap_open(newlib->name)) < 0) { |
221 if (fd == -1) { |
169 print_debug("can't open shared object %s\n", newlib->name); |
222 if ( (newlib->fd = pathmap_open(newlib->name)) < 0) { |
170 free(newlib); |
223 print_debug("can't open shared object %s\n", newlib->name); |
171 return NULL; |
|
172 } |
|
173 } else { |
|
174 newlib->fd = fd; |
|
175 } |
|
176 |
|
177 // check whether we have got an ELF file. /proc/<pid>/map |
|
178 // gives out all file mappings and not just shared objects |
|
179 if (is_elf_file(newlib->fd) == false) { |
|
180 close(newlib->fd); |
|
181 free(newlib); |
224 free(newlib); |
182 return NULL; |
225 return NULL; |
183 } |
226 } |
184 |
227 } else { |
185 newlib->symtab = build_symtab(newlib->fd); |
228 newlib->fd = fd; |
186 if (newlib->symtab == NULL) { |
229 } |
187 print_debug("symbol table build failed for %s\n", newlib->name); |
230 |
188 } |
231 #ifdef __APPLE__ |
189 else { |
232 // check whether we have got an Macho file. |
190 print_debug("built symbol table for %s\n", newlib->name); |
233 if (is_macho_file(newlib->fd) == false) { |
191 } |
234 close(newlib->fd); |
192 |
235 free(newlib); |
193 // even if symbol table building fails, we add the lib_info. |
236 print_debug("not a mach-o file\n"); |
194 // This is because we may need to read from the ELF file for core file |
237 return NULL; |
195 // address read functionality. lookup_symbol checks for NULL symtab. |
238 } |
196 if (ph->libs) { |
239 #else |
197 ph->lib_tail->next = newlib; |
240 // check whether we have got an ELF file. /proc/<pid>/map |
198 ph->lib_tail = newlib; |
241 // gives out all file mappings and not just shared objects |
199 } else { |
242 if (is_elf_file(newlib->fd) == false) { |
200 ph->libs = ph->lib_tail = newlib; |
243 close(newlib->fd); |
201 } |
244 free(newlib); |
202 ph->num_libs++; |
245 return NULL; |
203 |
246 } |
204 return newlib; |
247 #endif // __APPLE__ |
|
248 |
|
249 newlib->symtab = build_symtab(newlib->fd); |
|
250 if (newlib->symtab == NULL) { |
|
251 print_debug("symbol table build failed for %s\n", newlib->name); |
|
252 } else { |
|
253 print_debug("built symbol table for %s\n", newlib->name); |
|
254 } |
|
255 |
|
256 // even if symbol table building fails, we add the lib_info. |
|
257 // This is because we may need to read from the ELF file or MachO file for core file |
|
258 // address read functionality. lookup_symbol checks for NULL symtab. |
|
259 if (ph->libs) { |
|
260 ph->lib_tail->next = newlib; |
|
261 ph->lib_tail = newlib; |
|
262 } else { |
|
263 ph->libs = ph->lib_tail = newlib; |
|
264 } |
|
265 ph->num_libs++; |
|
266 return newlib; |
205 } |
267 } |
206 |
268 |
207 // lookup for a specific symbol |
269 // lookup for a specific symbol |
208 uintptr_t lookup_symbol(struct ps_prochandle* ph, const char* object_name, |
270 uintptr_t lookup_symbol(struct ps_prochandle* ph, const char* object_name, |
209 const char* sym_name) { |
271 const char* sym_name) { |
210 // ignore object_name. search in all libraries |
272 // ignore object_name. search in all libraries |
211 // FIXME: what should we do with object_name?? The library names are obtained |
273 // FIXME: what should we do with object_name?? The library names are obtained |
212 // by parsing /proc/<pid>/maps, which may not be the same as object_name. |
274 // by parsing /proc/<pid>/maps, which may not be the same as object_name. |
213 // What we need is a utility to map object_name to real file name, something |
275 // What we need is a utility to map object_name to real file name, something |
214 // dlopen() does by looking at LD_LIBRARY_PATH and /etc/ld.so.cache. For |
276 // dlopen() does by looking at LD_LIBRARY_PATH and /etc/ld.so.cache. For |
215 // now, we just ignore object_name and do a global search for the symbol. |
277 // now, we just ignore object_name and do a global search for the symbol. |
216 |
278 |
217 lib_info* lib = ph->libs; |
279 lib_info* lib = ph->libs; |
218 while (lib) { |
280 while (lib) { |
219 if (lib->symtab) { |
281 if (lib->symtab) { |
220 uintptr_t res = search_symbol(lib->symtab, lib->base, sym_name, NULL); |
282 uintptr_t res = search_symbol(lib->symtab, lib->base, sym_name, NULL); |
221 if (res) return res; |
283 if (res) return res; |
222 } |
284 } |
223 lib = lib->next; |
285 lib = lib->next; |
224 } |
286 } |
225 |
287 |
226 print_debug("lookup failed for symbol '%s' in obj '%s'\n", |
288 print_debug("lookup failed for symbol '%s' in obj '%s'\n", |
227 sym_name, object_name); |
289 sym_name, object_name); |
228 return (uintptr_t) NULL; |
290 return (uintptr_t) NULL; |
229 } |
291 } |
230 |
|
231 |
292 |
232 const char* symbol_for_pc(struct ps_prochandle* ph, uintptr_t addr, uintptr_t* poffset) { |
293 const char* symbol_for_pc(struct ps_prochandle* ph, uintptr_t addr, uintptr_t* poffset) { |
233 const char* res = NULL; |
294 const char* res = NULL; |
234 lib_info* lib = ph->libs; |
295 lib_info* lib = ph->libs; |
235 while (lib) { |
296 while (lib) { |
236 if (lib->symtab && addr >= lib->base) { |
297 if (lib->symtab && addr >= lib->base) { |
237 res = nearest_symbol(lib->symtab, addr - lib->base, poffset); |
298 res = nearest_symbol(lib->symtab, addr - lib->base, poffset); |
238 if (res) return res; |
299 if (res) return res; |
239 } |
300 } |
240 lib = lib->next; |
301 lib = lib->next; |
241 } |
302 } |
242 return NULL; |
303 return NULL; |
243 } |
304 } |
244 |
305 |
245 // add a thread to ps_prochandle |
306 // add a thread to ps_prochandle |
246 thread_info* add_thread_info(struct ps_prochandle* ph, pthread_t pthread_id, lwpid_t lwp_id) { |
307 sa_thread_info* add_thread_info(struct ps_prochandle* ph, pthread_t pthread_id, lwpid_t lwp_id) { |
247 thread_info* newthr; |
308 sa_thread_info* newthr; |
248 if ( (newthr = (thread_info*) calloc(1, sizeof(thread_info))) == NULL) { |
309 if ( (newthr = (sa_thread_info*) calloc(1, sizeof(sa_thread_info))) == NULL) { |
249 print_debug("can't allocate memory for thread_info\n"); |
310 print_debug("can't allocate memory for thread_info\n"); |
250 return NULL; |
311 return NULL; |
251 } |
312 } |
252 |
313 |
253 // initialize thread info |
314 // initialize thread info |
254 newthr->pthread_id = pthread_id; |
315 newthr->pthread_id = pthread_id; |
255 newthr->lwp_id = lwp_id; |
316 newthr->lwp_id = lwp_id; |
256 |
317 |
257 // add new thread to the list |
318 // add new thread to the list |
258 newthr->next = ph->threads; |
319 newthr->next = ph->threads; |
259 ph->threads = newthr; |
320 ph->threads = newthr; |
260 ph->num_threads++; |
321 ph->num_threads++; |
261 return newthr; |
322 return newthr; |
262 } |
323 } |
263 |
324 |
264 |
325 #ifndef __APPLE__ |
265 // struct used for client data from thread_db callback |
326 // struct used for client data from thread_db callback |
266 struct thread_db_client_data { |
327 struct thread_db_client_data { |
267 struct ps_prochandle* ph; |
328 struct ps_prochandle* ph; |
268 thread_info_callback callback; |
329 thread_info_callback callback; |
269 }; |
330 }; |
270 |
331 |
271 // callback function for libthread_db |
332 // callback function for libthread_db |
272 static int thread_db_callback(const td_thrhandle_t *th_p, void *data) { |
333 static int thread_db_callback(const td_thrhandle_t *th_p, void *data) { |
273 struct thread_db_client_data* ptr = (struct thread_db_client_data*) data; |
334 struct thread_db_client_data* ptr = (struct thread_db_client_data*) data; |
312 // delete thread agent |
373 // delete thread agent |
313 td_ta_delete(thread_agent); |
374 td_ta_delete(thread_agent); |
314 return true; |
375 return true; |
315 } |
376 } |
316 |
377 |
|
378 #endif // __APPLE__ |
317 |
379 |
318 // get number of threads |
380 // get number of threads |
319 int get_num_threads(struct ps_prochandle* ph) { |
381 int get_num_threads(struct ps_prochandle* ph) { |
320 return ph->num_threads; |
382 return ph->num_threads; |
321 } |
383 } |
322 |
384 |
323 // get lwp_id of n'th thread |
385 // get lwp_id of n'th thread |
324 lwpid_t get_lwp_id(struct ps_prochandle* ph, int index) { |
386 lwpid_t get_lwp_id(struct ps_prochandle* ph, int index) { |
325 int count = 0; |
387 int count = 0; |
326 thread_info* thr = ph->threads; |
388 sa_thread_info* thr = ph->threads; |
327 while (thr) { |
389 while (thr) { |
328 if (count == index) { |
390 if (count == index) { |
329 return thr->lwp_id; |
391 return thr->lwp_id; |
330 } |
392 } |
331 count++; |
393 count++; |
332 thr = thr->next; |
394 thr = thr->next; |
333 } |
395 } |
334 return -1; |
396 return 0; |
335 } |
397 } |
|
398 |
|
399 #ifdef __APPLE__ |
|
400 // set lwp_id of n'th thread |
|
401 bool set_lwp_id(struct ps_prochandle* ph, int index, lwpid_t lwpid) { |
|
402 int count = 0; |
|
403 sa_thread_info* thr = ph->threads; |
|
404 while (thr) { |
|
405 if (count == index) { |
|
406 thr->lwp_id = lwpid; |
|
407 return true; |
|
408 } |
|
409 count++; |
|
410 thr = thr->next; |
|
411 } |
|
412 return false; |
|
413 } |
|
414 |
|
415 // get regs of n-th thread, only used in fillThreads the first time called |
|
416 bool get_nth_lwp_regs(struct ps_prochandle* ph, int index, struct reg* regs) { |
|
417 int count = 0; |
|
418 sa_thread_info* thr = ph->threads; |
|
419 while (thr) { |
|
420 if (count == index) { |
|
421 break; |
|
422 } |
|
423 count++; |
|
424 thr = thr->next; |
|
425 } |
|
426 if (thr != NULL) { |
|
427 memcpy(regs, &thr->regs, sizeof(struct reg)); |
|
428 return true; |
|
429 } |
|
430 return false; |
|
431 } |
|
432 |
|
433 #endif // __APPLE__ |
336 |
434 |
337 // get regs for a given lwp |
435 // get regs for a given lwp |
338 bool get_lwp_regs(struct ps_prochandle* ph, lwpid_t lwp_id, struct reg* regs) { |
436 bool get_lwp_regs(struct ps_prochandle* ph, lwpid_t lwp_id, struct reg* regs) { |
339 return ph->ops->get_lwp_regs(ph, lwp_id, regs); |
437 return ph->ops->get_lwp_regs(ph, lwp_id, regs); |
340 } |
438 } |
341 |
439 |
342 // get number of shared objects |
440 // get number of shared objects |
343 int get_num_libs(struct ps_prochandle* ph) { |
441 int get_num_libs(struct ps_prochandle* ph) { |
344 return ph->num_libs; |
442 return ph->num_libs; |
345 } |
443 } |
346 |
444 |
347 // get name of n'th solib |
445 // get name of n'th solib |
348 const char* get_lib_name(struct ps_prochandle* ph, int index) { |
446 const char* get_lib_name(struct ps_prochandle* ph, int index) { |
349 int count = 0; |
447 int count = 0; |
350 lib_info* lib = ph->libs; |
448 lib_info* lib = ph->libs; |
351 while (lib) { |
449 while (lib) { |
352 if (count == index) { |
450 if (count == index) { |
353 return lib->name; |
451 return lib->name; |
354 } |
452 } |
355 count++; |
453 count++; |
356 lib = lib->next; |
454 lib = lib->next; |
357 } |
455 } |
358 return NULL; |
456 return NULL; |
359 } |
457 } |
360 |
458 |
361 // get base address of a lib |
459 // get base address of a lib |
362 uintptr_t get_lib_base(struct ps_prochandle* ph, int index) { |
460 uintptr_t get_lib_base(struct ps_prochandle* ph, int index) { |
363 int count = 0; |
461 int count = 0; |
364 lib_info* lib = ph->libs; |
462 lib_info* lib = ph->libs; |
365 while (lib) { |
463 while (lib) { |
366 if (count == index) { |
464 if (count == index) { |
367 return lib->base; |
465 return lib->base; |
368 } |
466 } |
369 count++; |
467 count++; |
370 lib = lib->next; |
468 lib = lib->next; |
371 } |
469 } |
372 return (uintptr_t)NULL; |
470 return (uintptr_t)NULL; |
373 } |
471 } |
374 |
472 |
375 bool find_lib(struct ps_prochandle* ph, const char *lib_name) { |
473 bool find_lib(struct ps_prochandle* ph, const char *lib_name) { |
376 lib_info *p = ph->libs; |
474 lib_info *p = ph->libs; |
377 while (p) { |
475 while (p) { |