aboutsummaryrefslogtreecommitdiff
path: root/gdb/solib-ia64-hpux.c
blob: 3b0bf4800f0c91152427c45019a2a4fb21d0160b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
/* Copyright (C) 2010-2015 Free Software Foundation, Inc.

   This file is part of GDB.

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 3 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */

#include "defs.h"
#include "ia64-tdep.h"
#include "ia64-hpux-tdep.h"
#include "solib-ia64-hpux.h"
#include "solist.h"
#include "solib.h"
#include "target.h"
#include "gdbtypes.h"
#include "inferior.h"
#include "gdbcore.h"
#include "regcache.h"
#include "opcode/ia64.h"
#include "symfile.h"
#include "objfiles.h"
#include "elf-bfd.h"

/* Need to define the following macro in order to get the complete
   load_module_desc struct definition in dlfcn.h  Otherwise, it doesn't
   match the size of the struct the loader is providing us during load
   events.  */
#define _LOAD_MODULE_DESC_EXT

#include <sys/ttrace.h>
#include <dlfcn.h>
#include <elf.h>
#include <service_mgr.h>

/* The following is to have access to the definition of type load_info_t.  */
#include <crt0.h>

/* The r32 pseudo-register number.

   Like all stacked registers, r32 is treated as a pseudo-register,
   because it is not always available for read/write via the ttrace
   interface.  */
/* This is a bit of a hack, as we duplicate something hidden inside
   ia64-tdep.c, but oh well...  */
#define IA64_R32_PSEUDO_REGNUM (IA64_NAT127_REGNUM + 2)

/* Our struct so_list private data structure.  */

struct lm_info
{
  /* The shared library module descriptor.  We extract this structure
     from the loader at the time the shared library gets mapped.  */
  struct load_module_desc module_desc;

  /* The text segment address as defined in the shared library object
     (this is not the address where this segment got loaded).  This
     field is initially set to zero, and computed lazily.  */
  CORE_ADDR text_start;

  /* The data segment address as defined in the shared library object
     (this is not the address where this segment got loaded).  This
     field is initially set to zero, and computed lazily.  */
  CORE_ADDR data_start;
};

/* The list of shared libraries currently mapped by the inferior.  */

static struct so_list *so_list_head = NULL;

/* Create a new so_list element.  The result should be deallocated
   when no longer in use.  */

static struct so_list *
new_so_list (char *so_name, struct load_module_desc module_desc)
{
  struct so_list *new_so;

  new_so = (struct so_list *) XCNEW (struct so_list);
  new_so->lm_info = (struct lm_info *) XCNEW (struct lm_info);
  new_so->lm_info->module_desc = module_desc;

  strncpy (new_so->so_name, so_name, SO_NAME_MAX_PATH_SIZE - 1);
  new_so->so_name[SO_NAME_MAX_PATH_SIZE - 1] = '\0';
  strcpy (new_so->so_original_name, new_so->so_name);

  return new_so;
}

/* Return non-zero if the instruction at the current PC is a breakpoint
   part of the dynamic loading process.

   We identify such instructions by checking that the instruction at
   the current pc is a break insn where no software breakpoint has been
   inserted by us.  We also verify that the operands have specific
   known values, to be extra certain.

   PTID is the ptid of the thread that should be checked, but this
   function also assumes that inferior_ptid is already equal to PTID.
   Ideally, we would like to avoid the requirement on inferior_ptid,
   but many routines still use the inferior_ptid global to access
   the relevant thread's register and memory.  We still have the ptid
   as parameter to be able to pass it to the routines that do take a ptid
   - that way we avoid increasing explicit uses of the inferior_ptid
   global.  */

static int
ia64_hpux_at_dld_breakpoint_1_p (ptid_t ptid)
{
  struct regcache *regcache = get_thread_regcache (ptid);
  CORE_ADDR pc = regcache_read_pc (regcache);
  struct address_space *aspace = get_regcache_aspace (regcache);
  ia64_insn t0, t1, slot[3], templ, insn;
  int slotnum;
  bfd_byte bundle[16];

  /* If this is a regular breakpoint, then it can not be a dld one.  */
  if (breakpoint_inserted_here_p (aspace, pc))
    return 0;

  slotnum = ((long) pc) & 0xf;
  if (slotnum > 2)
    internal_error (__FILE__, __LINE__,
		    "invalid slot (%d) for address %s", slotnum,
		    paddress (get_regcache_arch (regcache), pc));

  pc -= (pc & 0xf);
  read_memory (pc, bundle, sizeof (bundle));

  /* bundles are always in little-endian byte order */
  t0 = bfd_getl64 (bundle);
  t1 = bfd_getl64 (bundle + 8);
  templ = (t0 >> 1) & 0xf;
  slot[0] = (t0 >>  5) & 0x1ffffffffffLL;
  slot[1] = ((t0 >> 46) & 0x3ffff) | ((t1 & 0x7fffff) << 18);
  slot[2] = (t1 >> 23) & 0x1ffffffffffLL;

  if (templ == 2 && slotnum == 1)
    {
      /* skip L slot in MLI template: */
      slotnum = 2;
    }

  insn = slot[slotnum];

  return (insn == 0x1c0c9c0       /* break.i 0x070327 */
          || insn == 0x3c0c9c0);  /* break.i 0x0f0327 */
}

/* Same as ia64_hpux_at_dld_breakpoint_1_p above, with the following
   differences: It temporarily sets inferior_ptid to PTID, and also
   contains any exception being raised.  */

int
ia64_hpux_at_dld_breakpoint_p (ptid_t ptid)
{
  volatile struct gdb_exception e;
  ptid_t saved_ptid = inferior_ptid;
  int result = 0;

  inferior_ptid = ptid;
  TRY_CATCH (e, RETURN_MASK_ALL)
    {
      result = ia64_hpux_at_dld_breakpoint_1_p (ptid);
    }
  inferior_ptid = saved_ptid;
  if (e.reason < 0)
    warning (_("error while checking for dld breakpoint: %s"), e.message);

  return result;
}

/* Handler for library load event: Read the information provided by
   the loader, and then use it to read the shared library symbols.  */

static void
ia64_hpux_handle_load_event (struct regcache *regcache)
{
  CORE_ADDR module_desc_addr;
  ULONGEST module_desc_size;
  CORE_ADDR so_path_addr;
  char so_path[PATH_MAX];
  struct load_module_desc module_desc;
  struct so_list *new_so;

  /* Extract the data provided by the loader as follow:
       - r33: Address of load_module_desc structure
       - r34: size of struct load_module_desc
       - r35: Address of string holding shared library path
   */
  regcache_cooked_read_unsigned (regcache, IA64_R32_PSEUDO_REGNUM + 1,
                                 &module_desc_addr);
  regcache_cooked_read_unsigned (regcache, IA64_R32_PSEUDO_REGNUM + 2,
                                 &module_desc_size);
  regcache_cooked_read_unsigned (regcache, IA64_R32_PSEUDO_REGNUM + 3,
                                 &so_path_addr);

  if (module_desc_size != sizeof (struct load_module_desc))
    warning (_("load_module_desc size (%ld) != size returned by kernel (%s)"),
             sizeof (struct load_module_desc),
	     pulongest (module_desc_size));

  read_memory_string (so_path_addr, so_path, PATH_MAX);
  read_memory (module_desc_addr, (gdb_byte *) &module_desc,
	       sizeof (module_desc));

  /* Create a new so_list element and insert it at the start of our
     so_list_head (we insert at the start of the list only because
     it is less work compared to inserting it elsewhere).  */
  new_so = new_so_list (so_path, module_desc);
  new_so->next = so_list_head;
  so_list_head = new_so;
}

/* Update the value of the PC to point to the begining of the next
   instruction bundle.  */

static void
ia64_hpux_move_pc_to_next_bundle (struct regcache *regcache)
{
  CORE_ADDR pc = regcache_read_pc (regcache);

  pc -= pc & 0xf;
  pc += 16;
  ia64_write_pc (regcache, pc);
}

/* Handle loader events.

   PTID is the ptid of the thread corresponding to the event being
   handled.  Similarly to ia64_hpux_at_dld_breakpoint_1_p, this
   function assumes that inferior_ptid is set to PTID.  */

static void
ia64_hpux_handle_dld_breakpoint_1 (ptid_t ptid)
{
  struct regcache *regcache = get_thread_regcache (ptid);
  ULONGEST arg0;

  /* The type of event is provided by the loaded via r32.  */
  regcache_cooked_read_unsigned (regcache, IA64_R32_PSEUDO_REGNUM, &arg0);
  switch (arg0)
    {
      case BREAK_DE_SVC_LOADED:
	/* Currently, the only service loads are uld and dld,
	   so we shouldn't need to do anything.  Just ignore.  */
	break;
      case BREAK_DE_LIB_LOADED:
	ia64_hpux_handle_load_event (regcache);
	solib_add (NULL, 0, &current_target, auto_solib_add);
	break;
      case BREAK_DE_LIB_UNLOADED:
      case BREAK_DE_LOAD_COMPLETE:
      case BREAK_DE_BOR:
	/* Ignore for now.  */
	break;
    }

  /* Now that we have handled the event, we can move the PC to
     the next instruction bundle, past the break instruction.  */
  ia64_hpux_move_pc_to_next_bundle (regcache);
}

/* Same as ia64_hpux_handle_dld_breakpoint_1 above, with the following
   differences: This function temporarily sets inferior_ptid to PTID,
   and also contains any exception.  */

void
ia64_hpux_handle_dld_breakpoint (ptid_t ptid)
{
  volatile struct gdb_exception e;
  ptid_t saved_ptid = inferior_ptid;

  inferior_ptid = ptid;
  TRY_CATCH (e, RETURN_MASK_ALL)
    {
      ia64_hpux_handle_dld_breakpoint_1 (ptid);
    }
  inferior_ptid = saved_ptid;
  if (e.reason < 0)
    warning (_("error detected while handling dld breakpoint: %s"), e.message);
}

/* Find the address of the code and data segments in ABFD, and update
   TEXT_START and DATA_START accordingly.  */

static void
ia64_hpux_find_start_vma (bfd *abfd, CORE_ADDR *text_start,
                          CORE_ADDR *data_start)
{
  Elf_Internal_Ehdr *i_ehdrp = elf_elfheader (abfd);
  Elf64_Phdr phdr;
  int i;

  *text_start = 0;
  *data_start = 0;

  if (bfd_seek (abfd, i_ehdrp->e_phoff, SEEK_SET) == -1)
    error (_("invalid program header offset in %s"), abfd->filename);

  for (i = 0; i < i_ehdrp->e_phnum; i++)
    {
      if (bfd_bread (&phdr, sizeof (phdr), abfd) != sizeof (phdr))
        error (_("failed to read segment %d in %s"), i, abfd->filename);

      if (phdr.p_flags & PF_X
          && (*text_start == 0 || phdr.p_vaddr < *text_start))
        *text_start = phdr.p_vaddr;

      if (phdr.p_flags & PF_W
          && (*data_start == 0 || phdr.p_vaddr < *data_start))
        *data_start = phdr.p_vaddr;
    }
}

/* The "relocate_section_addresses" target_so_ops routine for ia64-hpux.  */

static void
ia64_hpux_relocate_section_addresses (struct so_list *so,
				      struct target_section *sec)
{
  CORE_ADDR offset = 0;

  /* If we haven't computed the text & data segment addresses, do so now.
     We do this here, because we now have direct access to the associated
     bfd, whereas we would have had to open our own if we wanted to do it
     while processing the library-load event.  */
  if (so->lm_info->text_start == 0 && so->lm_info->data_start == 0)
    ia64_hpux_find_start_vma (sec->the_bfd_section->owner,
			      &so->lm_info->text_start,
			      &so->lm_info->data_start);

  /* Determine the relocation offset based on which segment
     the section belongs to.  */
  if ((so->lm_info->text_start < so->lm_info->data_start
       && sec->addr < so->lm_info->data_start)
      || (so->lm_info->text_start > so->lm_info->data_start
          && sec->addr >= so->lm_info->text_start))
    offset = so->lm_info->module_desc.text_base - so->lm_info->text_start;
  else if ((so->lm_info->text_start < so->lm_info->data_start
            && sec->addr >= so->lm_info->data_start)
           || (so->lm_info->text_start > so->lm_info->data_start
	       && sec->addr < so->lm_info->text_start))
    offset = so->lm_info->module_desc.data_base - so->lm_info->data_start;

  /* And now apply the relocation.  */
  sec->addr += offset;
  sec->endaddr += offset;

  /* Best effort to set addr_high/addr_low.  This is used only by
     'info sharedlibrary'.  */
  if (so->addr_low == 0 || sec->addr < so->addr_low)
    so->addr_low = sec->addr;

  if (so->addr_high == 0 || sec->endaddr > so->addr_high)
    so->addr_high = sec->endaddr;
}

/* The "free_so" target_so_ops routine for ia64-hpux.  */

static void
ia64_hpux_free_so (struct so_list *so)
{
  xfree (so->lm_info);
}

/* The "clear_solib" target_so_ops routine for ia64-hpux.  */

static void
ia64_hpux_clear_solib (void)
{
  struct so_list *so;

  while (so_list_head != NULL)
    {
      so = so_list_head;
      so_list_head = so_list_head->next;

      ia64_hpux_free_so (so);
      xfree (so);
    }
}

/* Assuming the inferior just stopped on an EXEC event, return
   the address of the load_info_t structure.  */

static CORE_ADDR
ia64_hpux_get_load_info_addr (void)
{
  struct type *data_ptr_type = builtin_type (target_gdbarch ())->builtin_data_ptr;
  CORE_ADDR addr;
  int status;

  /* The address of the load_info_t structure is stored in the 4th
     argument passed to the initial thread of the process (in other
     words, in argv[3]).  So get the address of these arguments,
     and extract the 4th one.  */
  status = ttrace (TT_PROC_GET_ARGS, ptid_get_pid (inferior_ptid),
		   0, (uintptr_t) &addr, sizeof (CORE_ADDR), 0);
  if (status == -1 && errno)
    perror_with_name (_("Unable to get argument list"));
  return (read_memory_typed_address (addr + 3 * 8, data_ptr_type));
}

/* A structure used to aggregate some information extracted from
   the dynamic section of the main executable.  */

struct dld_info
{
  ULONGEST dld_flags;
  CORE_ADDR load_map;
};

/* Scan the ".dynamic" section referenced by ABFD and DYN_SECT,
   and extract the information needed to fill in INFO.  */

static void
ia64_hpux_read_dynamic_info (struct gdbarch *gdbarch, bfd *abfd,
			     asection *dyn_sect, struct dld_info *info)
{
  int sect_size;
  char *buf;
  char *buf_end;

  /* Make sure that info always has initialized data, even if we fail
     to read the syn_sect section.  */
  memset (info, 0, sizeof (struct dld_info));

  sect_size = bfd_section_size (abfd, dyn_sect);
  buf = alloca (sect_size);
  buf_end = buf + sect_size;

  if (bfd_seek (abfd, dyn_sect->filepos, SEEK_SET) != 0
      || bfd_bread (buf, sect_size, abfd) != sect_size)
    error (_("failed to read contents of .dynamic section"));

  for (; buf < buf_end; buf += sizeof (Elf64_Dyn))
    {
      Elf64_Dyn *dynp = (Elf64_Dyn *) buf;
      Elf64_Sxword d_tag;

      d_tag = bfd_h_get_64 (abfd, &dynp->d_tag);
      switch (d_tag)
        {
          case DT_HP_DLD_FLAGS:
            info->dld_flags = bfd_h_get_64 (abfd, &dynp->d_un);
            break;

          case DT_HP_LOAD_MAP:
            {
              CORE_ADDR load_map_addr = bfd_h_get_64 (abfd, &dynp->d_un.d_ptr);

              if (target_read_memory (load_map_addr,
				      (gdb_byte *) &info->load_map,
                                      sizeof (info->load_map)) != 0)
		error (_("failed to read load map at %s"),
		       paddress (gdbarch, load_map_addr));
            }
            break;
        }
    }
}

/* Wrapper around target_read_memory used with libdl.  */

static void *
ia64_hpux_read_tgt_mem (void *buffer, uint64_t ptr, size_t bufsiz, int ident)
{
  if (target_read_memory (ptr, (gdb_byte *) buffer, bufsiz) != 0)
    return 0;
  else
    return buffer;
}

/* Create a new so_list object for a shared library, and store that
   new so_list object in our SO_LIST_HEAD list.

   SO_INDEX is an index specifying the placement of the loaded shared
   library in the dynamic loader's search list.  Normally, this index
   is strictly positive, but an index of -1 refers to the loader itself.

   Return nonzero if the so_list object could be created.  A null
   return value with a positive SO_INDEX normally means that there are
   no more entries in the dynamic loader's search list at SO_INDEX or
   beyond.  */

static int
ia64_hpux_add_so_from_dld_info (struct dld_info info, int so_index)
{
  struct load_module_desc module_desc;
  uint64_t so_handle;
  char *so_path;
  struct so_list *so;

  so_handle = dlgetmodinfo (so_index, &module_desc, sizeof (module_desc),
			    ia64_hpux_read_tgt_mem, 0, info.load_map);

  if (so_handle == 0)
    /* No such entry.  We probably reached the end of the list.  */
    return 0;

  so_path = dlgetname (&module_desc, sizeof (module_desc),
                       ia64_hpux_read_tgt_mem, 0, info.load_map);
  if (so_path == NULL)
    {
      /* Should never happen, but let's not crash if it does.  */
      warning (_("unable to get shared library name, symbols not loaded"));
      return 0;
    }

  /* Create a new so_list and insert it at the start of our list.
     The order is not extremely important, but it's less work to do so
     at the end of the list.  */
  so = new_so_list (so_path, module_desc);
  so->next = so_list_head;
  so_list_head = so;

  return 1;
}

/* Assuming we just attached to a process, update our list of shared
   libraries (SO_LIST_HEAD) as well as GDB's list.  */

static void
ia64_hpux_solib_add_after_attach (void)
{
  bfd *abfd;
  asection *dyn_sect;
  struct dld_info info;
  int i;

  if (symfile_objfile == NULL)
    return;

  abfd = symfile_objfile->obfd;
  dyn_sect = bfd_get_section_by_name (abfd, ".dynamic");

  if (dyn_sect == NULL || bfd_section_size (abfd, dyn_sect) == 0)
    return;

  ia64_hpux_read_dynamic_info (get_objfile_arch (symfile_objfile), abfd,
			       dyn_sect, &info);

  if ((info.dld_flags & DT_HP_DEBUG_PRIVATE) == 0)
    {
      warning (_(
"The shared libraries were not privately mapped; setting a breakpoint\n\
in a shared library will not work until you rerun the program.\n\
Use the following command to enable debugging of shared libraries.\n\
chatr +dbg enable a.out"));
    }

  /* Read the symbols of the dynamic loader (dld.so).  */
  ia64_hpux_add_so_from_dld_info (info, -1);

  /* Read the symbols of all the other shared libraries.  */
  for (i = 1; ; i++)
    if (!ia64_hpux_add_so_from_dld_info (info, i))
      break;  /* End of list.  */

  /* Resync the library list at the core level.  */
  solib_add (NULL, 1, &current_target, auto_solib_add);
}

/* The "create_inferior_hook" target_so_ops routine for ia64-hpux.  */

static void
ia64_hpux_solib_create_inferior_hook (int from_tty)
{
  CORE_ADDR load_info_addr;
  load_info_t load_info;

  /* Initially, we were thinking about adding a check that the program
     (accessible through symfile_objfile) was linked against some shared
     libraries, by searching for a ".dynamic" section.  However, could
     this break in the case of a statically linked program that later
     uses dlopen?  Programs that are fully statically linked are very
     rare, and we will worry about them when we encounter one that
     causes trouble.  */

  /* Set the LI_TRACE flag in the load_info_t structure.  This enables
     notifications when shared libraries are being mapped.  */
  load_info_addr = ia64_hpux_get_load_info_addr ();
  read_memory (load_info_addr, (gdb_byte *) &load_info, sizeof (load_info));
  load_info.li_flags |= LI_TRACE;
  write_memory (load_info_addr, (gdb_byte *) &load_info, sizeof (load_info));

  /* If we just attached to our process, some shard libraries have
     already been mapped.  Find which ones they are...  */
  if (current_inferior ()->attach_flag)
    ia64_hpux_solib_add_after_attach ();
}

/* The "special_symbol_handling" target_so_ops routine for ia64-hpux.  */

static void
ia64_hpux_special_symbol_handling (void)
{
  /* Nothing to do.  */
}

/* The "current_sos" target_so_ops routine for ia64-hpux.  */

static struct so_list *
ia64_hpux_current_sos (void)
{
  /* Return a deep copy of our own list.  */
  struct so_list *new_head = NULL, *prev_new_so = NULL;
  struct so_list *our_so;

  for (our_so = so_list_head; our_so != NULL; our_so = our_so->next)
    {
      struct so_list *new_so;

      new_so = new_so_list (our_so->so_name, our_so->lm_info->module_desc);
      if (prev_new_so != NULL)
        prev_new_so->next = new_so;
      prev_new_so = new_so;
      if (new_head == NULL)
        new_head = new_so;
    }

  return new_head;
}

/* The "open_symbol_file_object" target_so_ops routine for ia64-hpux.  */

static int
ia64_hpux_open_symbol_file_object (void *from_ttyp)
{
  return 0;
}

/* The "in_dynsym_resolve_code" target_so_ops routine for ia64-hpux.  */

static int
ia64_hpux_in_dynsym_resolve_code (CORE_ADDR pc)
{
  return 0;
}

/* If FADDR is the address of a function inside one of the shared
   libraries, return the shared library linkage address.  */

CORE_ADDR
ia64_hpux_get_solib_linkage_addr (CORE_ADDR faddr)
{
  struct so_list *so = so_list_head;

  while (so != NULL)
    {
      struct load_module_desc module_desc = so->lm_info->module_desc;

      if (module_desc.text_base <= faddr
          && (module_desc.text_base + module_desc.text_size) > faddr)
        return module_desc.linkage_ptr;

      so = so->next;
    }

  return 0;
}

/* Create a new target_so_ops structure suitable for ia64-hpux, and
   return its address.  */

static struct target_so_ops *
ia64_hpux_target_so_ops (void)
{
  struct target_so_ops *ops = XCNEW (struct target_so_ops);

  ops->relocate_section_addresses = ia64_hpux_relocate_section_addresses;
  ops->free_so = ia64_hpux_free_so;
  ops->clear_solib = ia64_hpux_clear_solib;
  ops->solib_create_inferior_hook = ia64_hpux_solib_create_inferior_hook;
  ops->special_symbol_handling = ia64_hpux_special_symbol_handling;
  ops->current_sos = ia64_hpux_current_sos;
  ops->open_symbol_file_object = ia64_hpux_open_symbol_file_object;
  ops->in_dynsym_resolve_code = ia64_hpux_in_dynsym_resolve_code;
  ops->bfd_open = solib_bfd_open;

  return ops;
}

/* Prevent warning from -Wmissing-prototypes.  */
void _initialize_solib_ia64_hpux (void);

void
_initialize_solib_ia64_hpux (void)
{
  ia64_hpux_so_ops = ia64_hpux_target_so_ops ();
}