Commit | Line | Data |
---|---|---|
1b6bc7e0 CF |
1 | /* Read the export table symbols from a portable executable and |
2 | convert to internal format, for GDB. Used as a last resort if no | |
3 | debugging symbols recognized. | |
4 | ||
7b6bb8da JB |
5 | Copyright (C) 2003, 2007, 2008, 2009, 2010, 2011 |
6 | Free Software Foundation, Inc. | |
1b6bc7e0 CF |
7 | |
8 | This file is part of GDB. | |
9 | ||
10 | This program is free software; you can redistribute it and/or modify | |
11 | it under the terms of the GNU General Public License as published by | |
a9762ec7 | 12 | the Free Software Foundation; either version 3 of the License, or |
1b6bc7e0 CF |
13 | (at your option) any later version. |
14 | ||
15 | This program is distributed in the hope that it will be useful, | |
16 | but WITHOUT ANY WARRANTY; without even the implied warranty of | |
17 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
18 | GNU General Public License for more details. | |
19 | ||
20 | You should have received a copy of the GNU General Public License | |
a9762ec7 | 21 | along with this program. If not, see <http://www.gnu.org/licenses/>. |
1b6bc7e0 | 22 | |
aff410f1 | 23 | Contributed by Raoul M. Gough (RaoulGough@yahoo.co.uk). */ |
1b6bc7e0 CF |
24 | |
25 | #include "coff-pe-read.h" | |
26 | ||
1b6bc7e0 | 27 | #include "defs.h" |
81de56be | 28 | #include "bfd.h" |
1b6bc7e0 CF |
29 | #include "gdbtypes.h" |
30 | ||
31 | #include "symtab.h" | |
32 | #include "symfile.h" | |
33 | #include "objfiles.h" | |
34 | ||
35 | /* Internal section information */ | |
36 | ||
37 | struct read_pe_section_data | |
38 | { | |
aff410f1 MS |
39 | CORE_ADDR vma_offset; /* Offset to loaded address of section. */ |
40 | unsigned long rva_start; /* Start offset within the pe. */ | |
41 | unsigned long rva_end; /* End offset within the pe. */ | |
42 | enum minimal_symbol_type ms_type; /* Type to assign symbols in | |
43 | section. */ | |
1b6bc7e0 CF |
44 | }; |
45 | ||
46 | #define PE_SECTION_INDEX_TEXT 0 | |
47 | #define PE_SECTION_INDEX_DATA 1 | |
48 | #define PE_SECTION_INDEX_BSS 2 | |
49 | #define PE_SECTION_TABLE_SIZE 3 | |
50 | #define PE_SECTION_INDEX_INVALID -1 | |
51 | \f | |
52 | /* Get the index of the named section in our own array, which contains | |
aff410f1 MS |
53 | text, data and bss in that order. Return PE_SECTION_INDEX_INVALID |
54 | if passed an unrecognised section name. */ | |
1b6bc7e0 CF |
55 | |
56 | static int | |
57 | read_pe_section_index (const char *section_name) | |
58 | { | |
59 | if (strcmp (section_name, ".text") == 0) | |
60 | { | |
61 | return PE_SECTION_INDEX_TEXT; | |
62 | } | |
63 | ||
64 | else if (strcmp (section_name, ".data") == 0) | |
65 | { | |
66 | return PE_SECTION_INDEX_DATA; | |
67 | } | |
68 | ||
69 | else if (strcmp (section_name, ".bss") == 0) | |
70 | { | |
71 | return PE_SECTION_INDEX_BSS; | |
72 | } | |
73 | ||
74 | else | |
75 | { | |
76 | return PE_SECTION_INDEX_INVALID; | |
77 | } | |
78 | } | |
79 | ||
aff410f1 | 80 | /* Record the virtual memory address of a section. */ |
1b6bc7e0 CF |
81 | |
82 | static void | |
83 | get_section_vmas (bfd *abfd, asection *sectp, void *context) | |
84 | { | |
85 | struct read_pe_section_data *sections = context; | |
86 | int sectix = read_pe_section_index (sectp->name); | |
87 | ||
88 | if (sectix != PE_SECTION_INDEX_INVALID) | |
89 | { | |
90 | /* Data within the section start at rva_start in the pe and at | |
aff410f1 | 91 | bfd_get_section_vma() within memory. Store the offset. */ |
1b6bc7e0 CF |
92 | |
93 | sections[sectix].vma_offset | |
94 | = bfd_get_section_vma (abfd, sectp) - sections[sectix].rva_start; | |
95 | } | |
96 | } | |
97 | \f | |
aff410f1 | 98 | /* Create a minimal symbol entry for an exported symbol. */ |
1b6bc7e0 CF |
99 | |
100 | static void | |
101 | add_pe_exported_sym (char *sym_name, | |
102 | unsigned long func_rva, | |
103 | const struct read_pe_section_data *section_data, | |
104 | const char *dll_name, struct objfile *objfile) | |
105 | { | |
aff410f1 | 106 | /* Add the stored offset to get the loaded address of the symbol. */ |
1b6bc7e0 CF |
107 | |
108 | CORE_ADDR vma = func_rva + section_data->vma_offset; | |
109 | ||
110 | char *qualified_name = 0; | |
111 | int dll_name_len = strlen (dll_name); | |
1b6bc7e0 CF |
112 | |
113 | /* Generate a (hopefully unique) qualified name using the first part | |
aff410f1 MS |
114 | of the dll name, e.g. KERNEL32!AddAtomA. This matches the style |
115 | used by windbg from the "Microsoft Debugging Tools for Windows". */ | |
1b6bc7e0 CF |
116 | |
117 | qualified_name = xmalloc (dll_name_len + strlen (sym_name) + 2); | |
118 | ||
119 | strncpy (qualified_name, dll_name, dll_name_len); | |
120 | qualified_name[dll_name_len] = '!'; | |
121 | strcpy (qualified_name + dll_name_len + 1, sym_name); | |
122 | ||
123 | prim_record_minimal_symbol (qualified_name, | |
124 | vma, section_data->ms_type, objfile); | |
125 | ||
126 | xfree (qualified_name); | |
127 | ||
aff410f1 MS |
128 | /* Enter the plain name as well, which might not be unique. */ |
129 | prim_record_minimal_symbol (sym_name, vma, | |
130 | section_data->ms_type, objfile); | |
1b6bc7e0 CF |
131 | } |
132 | ||
aff410f1 | 133 | /* Truncate a dll_name at the first dot character. */ |
1b6bc7e0 CF |
134 | |
135 | static void | |
136 | read_pe_truncate_name (char *dll_name) | |
137 | { | |
138 | while (*dll_name) | |
139 | { | |
140 | if ((*dll_name) == '.') | |
141 | { | |
aff410f1 | 142 | *dll_name = '\0'; /* truncates and causes loop exit. */ |
1b6bc7e0 CF |
143 | } |
144 | ||
145 | else | |
146 | { | |
147 | ++dll_name; | |
148 | } | |
149 | } | |
150 | } | |
151 | \f | |
aff410f1 | 152 | /* Low-level support functions, direct from the ld module pe-dll.c. */ |
1b6bc7e0 CF |
153 | static unsigned int |
154 | pe_get16 (bfd *abfd, int where) | |
155 | { | |
156 | unsigned char b[2]; | |
157 | ||
158 | bfd_seek (abfd, (file_ptr) where, SEEK_SET); | |
159 | bfd_bread (b, (bfd_size_type) 2, abfd); | |
160 | return b[0] + (b[1] << 8); | |
161 | } | |
162 | ||
163 | static unsigned int | |
164 | pe_get32 (bfd *abfd, int where) | |
165 | { | |
166 | unsigned char b[4]; | |
167 | ||
168 | bfd_seek (abfd, (file_ptr) where, SEEK_SET); | |
169 | bfd_bread (b, (bfd_size_type) 4, abfd); | |
170 | return b[0] + (b[1] << 8) + (b[2] << 16) + (b[3] << 24); | |
171 | } | |
172 | ||
173 | static unsigned int | |
174 | pe_as32 (void *ptr) | |
175 | { | |
176 | unsigned char *b = ptr; | |
177 | ||
178 | return b[0] + (b[1] << 8) + (b[2] << 16) + (b[3] << 24); | |
179 | } | |
180 | \f | |
181 | /* Read the (non-debug) export symbol table from a portable | |
aff410f1 MS |
182 | executable. Code originally lifted from the ld function |
183 | pe_implied_import_dll in pe-dll.c. */ | |
1b6bc7e0 CF |
184 | |
185 | void | |
186 | read_pe_exported_syms (struct objfile *objfile) | |
187 | { | |
188 | bfd *dll = objfile->obfd; | |
189 | unsigned long pe_header_offset, opthdr_ofs, num_entries, i; | |
190 | unsigned long export_rva, export_size, nsections, secptr, expptr; | |
191 | unsigned long exp_funcbase; | |
192 | unsigned char *expdata, *erva; | |
193 | unsigned long name_rvas, ordinals, nexp, ordbase; | |
194 | char *dll_name; | |
a68ddad5 KT |
195 | int is_pe64 = 0; |
196 | int is_pe32 = 0; | |
1b6bc7e0 CF |
197 | |
198 | /* Array elements are for text, data and bss in that order | |
199 | Initialization with start_rva > end_rva guarantees that | |
aff410f1 | 200 | unused sections won't be matched. */ |
1b6bc7e0 CF |
201 | struct read_pe_section_data section_data[PE_SECTION_TABLE_SIZE] |
202 | = { {0, 1, 0, mst_text}, | |
203 | {0, 1, 0, mst_data}, | |
204 | {0, 1, 0, mst_bss} | |
205 | }; | |
206 | ||
207 | struct cleanup *back_to = 0; | |
208 | ||
209 | char const *target = bfd_get_target (objfile->obfd); | |
210 | ||
5e13bd89 PA |
211 | is_pe64 = (strcmp (target, "pe-x86-64") == 0 |
212 | || strcmp (target, "pei-x86-64") == 0); | |
213 | is_pe32 = (strcmp (target, "pe-i386") == 0 | |
214 | || strcmp (target, "pei-i386") == 0 | |
215 | || strcmp (target, "pe-arm-wince-little") == 0 | |
216 | || strcmp (target, "pei-arm-wince-little") == 0); | |
217 | ||
a68ddad5 | 218 | if (!is_pe32 && !is_pe64) |
1b6bc7e0 | 219 | { |
5e13bd89 PA |
220 | /* This is not a recognized PE format file. Abort now, because |
221 | the code is untested on anything else. *FIXME* test on | |
aff410f1 | 222 | further architectures and loosen or remove this test. */ |
1b6bc7e0 CF |
223 | return; |
224 | } | |
225 | ||
226 | /* Get pe_header, optional header and numbers of export entries. */ | |
227 | pe_header_offset = pe_get32 (dll, 0x3c); | |
228 | opthdr_ofs = pe_header_offset + 4 + 20; | |
a68ddad5 | 229 | if (is_pe64) |
1dac1b47 | 230 | num_entries = pe_get32 (dll, opthdr_ofs + 108); |
a68ddad5 KT |
231 | else |
232 | num_entries = pe_get32 (dll, opthdr_ofs + 92); | |
1b6bc7e0 CF |
233 | |
234 | if (num_entries < 1) /* No exports. */ | |
235 | { | |
236 | return; | |
237 | } | |
238 | ||
a68ddad5 KT |
239 | if (is_pe64) |
240 | { | |
241 | export_rva = pe_get32 (dll, opthdr_ofs + 112); | |
242 | export_size = pe_get32 (dll, opthdr_ofs + 116); | |
243 | } | |
244 | else | |
245 | { | |
246 | export_rva = pe_get32 (dll, opthdr_ofs + 96); | |
247 | export_size = pe_get32 (dll, opthdr_ofs + 100); | |
248 | } | |
1b6bc7e0 CF |
249 | nsections = pe_get16 (dll, pe_header_offset + 4 + 2); |
250 | secptr = (pe_header_offset + 4 + 20 + | |
251 | pe_get16 (dll, pe_header_offset + 4 + 16)); | |
252 | expptr = 0; | |
253 | ||
254 | /* Get the rva and size of the export section. */ | |
255 | for (i = 0; i < nsections; i++) | |
256 | { | |
257 | char sname[8]; | |
258 | unsigned long secptr1 = secptr + 40 * i; | |
259 | unsigned long vaddr = pe_get32 (dll, secptr1 + 12); | |
260 | unsigned long vsize = pe_get32 (dll, secptr1 + 16); | |
261 | unsigned long fptr = pe_get32 (dll, secptr1 + 20); | |
262 | ||
263 | bfd_seek (dll, (file_ptr) secptr1, SEEK_SET); | |
264 | bfd_bread (sname, (bfd_size_type) 8, dll); | |
265 | ||
266 | if (vaddr <= export_rva && vaddr + vsize > export_rva) | |
267 | { | |
268 | expptr = fptr + (export_rva - vaddr); | |
269 | if (export_rva + export_size > vaddr + vsize) | |
270 | export_size = vsize - (export_rva - vaddr); | |
271 | break; | |
272 | } | |
273 | } | |
274 | ||
275 | if (export_size == 0) | |
276 | { | |
aff410f1 | 277 | /* Empty export table. */ |
1b6bc7e0 CF |
278 | return; |
279 | } | |
280 | ||
aff410f1 MS |
281 | /* Scan sections and store the base and size of the relevant |
282 | sections. */ | |
1b6bc7e0 CF |
283 | for (i = 0; i < nsections; i++) |
284 | { | |
285 | unsigned long secptr1 = secptr + 40 * i; | |
286 | unsigned long vsize = pe_get32 (dll, secptr1 + 8); | |
287 | unsigned long vaddr = pe_get32 (dll, secptr1 + 12); | |
1b6bc7e0 CF |
288 | char sec_name[9]; |
289 | int sectix; | |
290 | ||
291 | sec_name[8] = '\0'; | |
292 | bfd_seek (dll, (file_ptr) secptr1 + 0, SEEK_SET); | |
293 | bfd_bread (sec_name, (bfd_size_type) 8, dll); | |
294 | ||
295 | sectix = read_pe_section_index (sec_name); | |
296 | ||
297 | if (sectix != PE_SECTION_INDEX_INVALID) | |
298 | { | |
299 | section_data[sectix].rva_start = vaddr; | |
300 | section_data[sectix].rva_end = vaddr + vsize; | |
301 | } | |
302 | } | |
303 | ||
304 | expdata = (unsigned char *) xmalloc (export_size); | |
305 | back_to = make_cleanup (xfree, expdata); | |
306 | ||
307 | bfd_seek (dll, (file_ptr) expptr, SEEK_SET); | |
308 | bfd_bread (expdata, (bfd_size_type) export_size, dll); | |
309 | erva = expdata - export_rva; | |
310 | ||
311 | nexp = pe_as32 (expdata + 24); | |
312 | name_rvas = pe_as32 (expdata + 32); | |
313 | ordinals = pe_as32 (expdata + 36); | |
314 | ordbase = pe_as32 (expdata + 16); | |
315 | exp_funcbase = pe_as32 (expdata + 28); | |
316 | ||
aff410f1 | 317 | /* Use internal dll name instead of full pathname. */ |
1b6bc7e0 CF |
318 | dll_name = pe_as32 (expdata + 12) + erva; |
319 | ||
320 | bfd_map_over_sections (dll, get_section_vmas, section_data); | |
321 | ||
322 | /* Adjust the vma_offsets in case this PE got relocated. This | |
323 | assumes that *all* sections share the same relocation offset | |
aff410f1 | 324 | as the text section. */ |
1b6bc7e0 CF |
325 | for (i = 0; i < PE_SECTION_TABLE_SIZE; i++) |
326 | { | |
327 | section_data[i].vma_offset | |
328 | += ANOFFSET (objfile->section_offsets, SECT_OFF_TEXT (objfile)); | |
329 | } | |
330 | ||
1b6bc7e0 | 331 | /* Truncate name at first dot. Should maybe also convert to all |
aff410f1 | 332 | lower case for convenience on Windows. */ |
1b6bc7e0 CF |
333 | read_pe_truncate_name (dll_name); |
334 | ||
335 | /* Iterate through the list of symbols. */ | |
336 | for (i = 0; i < nexp; i++) | |
337 | { | |
338 | /* Pointer to the names vector. */ | |
339 | unsigned long name_rva = pe_as32 (erva + name_rvas + i * 4); | |
340 | ||
341 | /* Pointer to the function address vector. */ | |
342 | unsigned long func_rva = pe_as32 (erva + exp_funcbase + i * 4); | |
343 | ||
aff410f1 | 344 | /* Find this symbol's section in our own array. */ |
1b6bc7e0 CF |
345 | int sectix = 0; |
346 | ||
347 | for (sectix = 0; sectix < PE_SECTION_TABLE_SIZE; ++sectix) | |
348 | { | |
349 | if ((func_rva >= section_data[sectix].rva_start) | |
350 | && (func_rva < section_data[sectix].rva_end)) | |
351 | { | |
352 | add_pe_exported_sym (erva + name_rva, | |
353 | func_rva, | |
354 | section_data + sectix, dll_name, objfile); | |
355 | break; | |
356 | } | |
357 | } | |
358 | } | |
359 | ||
aff410f1 | 360 | /* Discard expdata. */ |
1b6bc7e0 CF |
361 | do_cleanups (back_to); |
362 | } |