ChangeLog: ---------- 2002-09-16 Nathan Tallent * ecofflink.c: Fix the reading of the debugging information of Tru64/Alpha binaries that are produced by recent Compaq compilers. (mk_fdrtab): Fix error in creating the FDR (file descriptor) table. (lookup_line): Because of the strange information sometimes generated by Compaq's recent compilers, change how the FDR table is searched so that PDRs (procedure descriptors) are correctly found. Note that this change is really more of a hack; however, I have included extensive documentation as to why this is the best solution short of an extensive rewrite or another hack. (fdrtab_lookup): Add comments to explain the algorithm. Patch: bfd/ecofflink.c (created with 'cvs diff -c3p' against cvs repository on 9/16/02) ------ Index: ecofflink.c =================================================================== RCS file: /cvs/src/src/bfd/ecofflink.c,v retrieving revision 1.8 diff -c -3 -p -r1.8 ecofflink.c *** ecofflink.c 25 Jun 2002 06:21:51 -0000 1.8 --- ecofflink.c 16 Sep 2002 19:27:30 -0000 *************** mk_fdrtab (abfd, debug_info, debug_swap, *** 1879,1896 **** if (!stabs) { ! bfd_size_type external_pdr_size; ! char *pdr_ptr; ! PDR pdr; ! ! external_pdr_size = debug_swap->external_pdr_size; ! ! pdr_ptr = ((char *) debug_info->external_pdr ! + fdr_ptr->ipdFirst * external_pdr_size); ! (*debug_swap->swap_pdr_in) (abfd, (PTR) pdr_ptr, &pdr); /* The address of the first PDR is the offset of that procedure relative to the beginning of file FDR. */ ! tab->base_addr = fdr_ptr->adr - pdr.adr; } else { --- 1879,1910 ---- if (!stabs) { ! /* eraxxon@alumni.rice.edu: There are at least two problems ! with this computation: 1) PDRs do *not* contain offsets ! but full vma's; and typically the address of the first ! PDR is the address of the FDR, which will make (most) of ! the results of the original computation 0! 2) Once in a ! wacky while, the Compaq compiler generated PDR addresses ! do not equal the FDR vma, but they (the PDR address) are ! still vma's and not offsets. Cf. comments in ! 'lookup_line'. */ ! ! /* ORIG: ! bfd_size_type external_pdr_size; ! char *pdr_ptr; ! PDR pdr; ! ! external_pdr_size = debug_swap->external_pdr_size; ! ! pdr_ptr = ((char *) debug_info->external_pdr ! + fdr_ptr->ipdFirst * external_pdr_size); ! (*debug_swap->swap_pdr_in) (abfd, (PTR) pdr_ptr, &pdr); ! */ ! /* The address of the first PDR is the offset of that procedure relative to the beginning of file FDR. */ ! /* ORIG: tab->base_addr = fdr_ptr->adr - pdr.adr; */ ! tab->base_addr = fdr_ptr->adr; } else { *************** fdrtab_lookup (line_info, offset) *** 1931,1936 **** --- 1945,1951 ---- for (low = 0, high = len - 1 ; low != high ;) { mid = (high + low) / 2; + if (offset >= tab[mid].base_addr && offset < tab[mid + 1].base_addr) goto find_min; *************** fdrtab_lookup (line_info, offset) *** 1939,1944 **** --- 1954,1965 ---- else low = mid + 1; } + /* eraxxon@alumni.rice.edu: at this point 'offset' is either lower + than the lowest entry or higher than the highest entry. In the + former case high = low = mid = 0; we want to return -1. In the + latter case, low = high and mid = low - 1; we want to return the + index of the highest entry. Only in former case will the + following 'catch-all' test be true. */ ++mid; /* last entry is catch-all for all higher addresses: */ *************** fdrtab_lookup (line_info, offset) *** 1947,1952 **** --- 1968,1975 ---- find_min: + /* eraxxon: There may be multiple FDRs in the table with the same + base_addr; make sure that we are at the first one */ while (mid > 0 && tab[mid - 1].base_addr == tab[mid].base_addr) --mid; *************** lookup_line (abfd, debug_info, debug_swa *** 1969,1974 **** --- 1992,1999 ---- FDR *fdr_ptr; int i; + /* eraxxon@alumni.rice.edu: note that 'offset' is the full vma, not + a section offset */ offset = line_info->cache.start; /* Build FDR table (sorted by object file's base-address) if we *************** lookup_line (abfd, debug_info, debug_swa *** 1983,1988 **** --- 2008,2091 ---- i = fdrtab_lookup (line_info, offset); if (i < 0) return false; /* no FDR, no fun... */ + + /* eraxxon@alumni.rice.edu: 'fdrtab_lookup' doesn't give what we + want, at least for Compaq's C++ compiler 6.2. Consider three + FDRs with starting addresses of x, y, and z, respectively, such + that x < y < z. Assume further that y < 'offset' < z. It is + possble at times that the PDR for 'offset' is associated with FDR + x and *not* with FDR y. Erg!! + + From a binary dump of my C++ test case 'moo' using Compaq's + coffobjanl (output format has been edited for our purposes): + + FDR [2]: (main.C): First instruction: 0x12000207c + PDR [5] for File [2]: LoopTest__Xv <0x1200020a0> (a) + PDR [7] for File [2]: foo__Xv <0x120002168> + FDR [1]: (-1): First instruction: 0x1200020e8 + PDR [3] for File [1]: <0x120001ad0> (b) + FDR [6]: (-1): First instruction: 0x1200026f0 + + (a) In the case of PDR5, the vma is such that the first few + instructions of the procedure can be found. But since the size + of this procedure is 160b, the vma will soon cross into the + 'address space' of FDR1 and no debugging info will be found. How + repugnant! + + (b) It is also possible for a PDR to have a *lower* vma than its + associated FDR; see FDR1 and PDR3. Gross! + + Since the FDRs that are causing so much havok (in this case) 1) + do not describe actual files (fdr.rss == -1), and 2) contain only + compiler genarated routines, I thought a simple fix would be to + exclude them from the FDR table in 'mk_fdrtab'. But, besides not + knowing for certain whether this would be correct, it creates an + additional problem. If we happen to ask for source file info on + a compiler generated (procedure) symbol -- which is still in the + symbol table -- the result can be information from a real + procedure! This is because compiler generated procedures with + vma's higher than the last FDR in the fdr table will be + associated with a PDR from this FDR, specifically the PDR with + the highest vma. This wasn't a problem before, because each + procedure had a PDR. (Yes, this problem could be eliminated if + we kept the size of the last PDR around, but things are already + getting ugly.) + + Probably, a better solution would be to have a sorted PDR table. + Each PDR would have a pointer to its FDR so file information + could still be obtained. A FDR table could still be constructed + if necessary -- since it only contains pointers, not much extra + memory would be used -- but the PDR table would be searched to + locate debugging info. + + There is still at least one remaining issue. Sometimes a FDR can + have a bogus name, but contain PDRs that should belong to another + FDR with a real name. E.g: + + FDR [3]: 0000000120001b50 (/home/.../Array.H~alt~deccxx_5E5A62AD) + PDR [a] for File [3]: 0000000120001b50 + PDR [b] for File [3]: 0000000120001cf0 + PDR [c] for File [3]: 0000000120001dc8 + PDR [d] for File [3]: 0000000120001e40 + PDR [e] for File [3]: 0000000120001eb8 + PDR [f] for File [3]: 0000000120001f4c + FDR [4]: 0000000120001b50 (/home/.../Array.H) + + Here, FDR4 has the correct name, but should (seemingly) contain + PDRa-f. The symbol table for PDR4 does contain symbols for + PDRa-f, but so does the symbol table for FDR3. However the + former is different; perhaps this can be detected easily. (I'm + not sure at this point.) This problem only seems to be + associated with files with templates. I am assuming the idea is + that there is a 'fake' FDR (with PDRs) for each differently typed + set of templates that must be generated. Currently, FDR4 is + completely excluded from the FDR table in 'mk_fdrtab' because it + contains no PDRs. + + Since I don't have time to prepare a real fix for this right now, + be prepared for 'A Horrible Hack' to force the inspection of all + non-stabs FDRs. It's coming... */ + fdr_ptr = tab[i].fdr; /* Check whether this file has stabs debugging information. In a *************** lookup_line (abfd, debug_info, debug_swa *** 2070,2083 **** considerably, which is undesirable. */ external_pdr_size = debug_swap->external_pdr_size; ! /* Make offset relative to object file's start-address: */ ! offset -= tab[i].base_addr; /* Search FDR list starting at tab[i] for the PDR that best matches OFFSET. Normally, the FDR list is only one entry long. */ best_fdr = NULL; do { ! bfd_vma dist, min_dist = 0; char *pdr_hold; char *pdr_end; --- 2173,2203 ---- considerably, which is undesirable. */ external_pdr_size = debug_swap->external_pdr_size; ! /* eraxxon: PDR addresses (pdr.adr) are not relative to FDRs! Leave ! 'offset' alone. */ ! /*ORIG: /* Make offset relative to object file's start-address: */ ! /*ORIG: offset -= tab[i].base_addr;*/ ! ! /* eraxxon: The Horrible Hack: Because of the problems above, ! set i to 0 so we look through all FDRs. ! ! Because FDR's without any symbols are assumed to be ! non-stabs, searching through all FDRs may cause the following ! code to try to read stabs FDRs as ECOFF ones. However, I ! don't think this will harm anything. */ ! i = 0; ! /* Search FDR list starting at tab[i] for the PDR that best matches OFFSET. Normally, the FDR list is only one entry long. */ best_fdr = NULL; do { ! /* eraxxon: 'dist' and 'min_dist' can be negative now ! because we iterate over every FDR rather than just ones ! with a base address less than or equal to 'offset'. */ ! /*ORIG: bfd_vma dist, min_dist = 0; */ ! long dist = -1, min_dist = -1; ! char *pdr_hold; char *pdr_end; *************** lookup_line (abfd, debug_info, debug_swa *** 2100,2106 **** if (offset >= (pdr.adr - 0x10 * pdr.prof)) { dist = offset - (pdr.adr - 0x10 * pdr.prof); ! if (!pdr_hold || dist < min_dist) { min_dist = dist; pdr_hold = pdr_ptr; --- 2220,2231 ---- if (offset >= (pdr.adr - 0x10 * pdr.prof)) { dist = offset - (pdr.adr - 0x10 * pdr.prof); ! ! /* eraxxon: 'dist' can be negative now. Note that ! 'min_dist' can be negative if 'pdr_hold' below is ! NULL. */ ! /*ORIG: if (!pdr_hold || dist < min_dist)*/ ! if (!pdr_hold || (dist >= 0 && dist < min_dist)) { min_dist = dist; pdr_hold = pdr_ptr; *************** lookup_line (abfd, debug_info, debug_swa *** 2108,2123 **** } } ! if (!best_pdr || min_dist < best_dist) { ! best_dist = min_dist; best_fdr = fdr_ptr; best_pdr = pdr_hold; } /* continue looping until base_addr of next entry is different: */ } ! while (++i < line_info->fdrtab_len ! && tab[i].base_addr == tab[i - 1].base_addr); if (!best_fdr || !best_pdr) return false; /* shouldn't happen... */ --- 2233,2255 ---- } } ! /* eraxxon: 'min_dist' can be negative. */ ! /*ORIG: if (!best_pdr || min_dist < best_dist)*/ ! if (!best_pdr || (min_dist >= 0 && min_dist < best_dist)) { ! /* eraxxon: add cast */ ! /*ORIG: best_dist = min_dist;*/ ! best_dist = (bfd_vma) min_dist; best_fdr = fdr_ptr; best_pdr = pdr_hold; } /* continue looping until base_addr of next entry is different: */ } ! /* eraxxon: We want to iterate over all FDRs. See eraxxon's ! previous comment about 'fdrtab_lookup'. */ ! /*ORIG: while (++i < line_info->fdrtab_len ! && tab[i].base_addr == tab[i - 1].base_addr);*/ ! while (++i < line_info->fdrtab_len); if (!best_fdr || !best_pdr) return false; /* shouldn't happen... */