summaryrefslogtreecommitdiffstats
path: root/mdk-stage1/insmod-modutils/obj/obj_kallsyms.c
blob: 1836141e901ebf439be1ae855cbabc36d68f09f1 (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
/* Build a section containing all non-stack symbols.
   Copyright 2000 Keith Owens <kaos@ocs.com.au>

   This file is part of the Linux modutils.

   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 2 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, write to the Free Software Foundation,
   Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.  */

#include <stdlib.h>
#include <string.h>
#include <malloc.h>

#include "obj.h"
#include "kallsyms.h"
#include "util.h"

/*======================================================================*/

#define EXPAND_BY 4096  /* Arbitrary */

/* Append a string to the big list of strings */

static void
append_string (const char *s, char **strings,
	       ElfW(Word) *strings_size, ElfW(Word) *strings_left)
{
    size_t l = strlen(s) + 1;
    while (l > *strings_left) {
	*strings = xrealloc(*strings, *strings_size += EXPAND_BY);
	*strings_left += EXPAND_BY;
    }
    memcpy((char *)*strings+*strings_size-*strings_left, s, l);
    *strings_left -= l;
}


/* Append a symbol to the big list of symbols */

static void
append_symbol (const struct kallsyms_symbol *s,
	       struct kallsyms_symbol **symbols,
	       ElfW(Word) *symbols_size, ElfW(Word) *symbols_left)
{
    size_t l = sizeof(*s);
    while (l > *symbols_left) {
	*symbols = xrealloc(*symbols, *symbols_size += EXPAND_BY);
	*symbols_left += EXPAND_BY;
    }
    memcpy((char *)*symbols+*symbols_size-*symbols_left, s, l);
    *symbols_left -= l;
}

/* qsort compare routine to sort symbols */

static const char *sym_strings;

static int
symbol_compare (const void *a, const void *b)
{
    struct kallsyms_symbol *c = (struct kallsyms_symbol *) a;
    struct kallsyms_symbol *d = (struct kallsyms_symbol *) b;

    if (c->symbol_addr > d->symbol_addr)
	return(1);
    if (c->symbol_addr < d->symbol_addr)
	return(-1);
    return(strcmp(sym_strings+c->name_off, sym_strings+d->name_off));
}


/* Extract all symbols from the input obj_file, ignore ones that are
 * no use for debugging, build an output obj_file containing only the
 * kallsyms section.
 *
 * The kallsyms section is a bit unusual.  It deliberately has no
 * relocatable data, all "pointers" are represented as byte offsets
 * into the the section.  This means it can be stored anywhere without
 * relocation problems.  In particular it can be stored within a kernel
 * image, it can be stored separately from the kernel image, it can be
 * appended to a module just before loading, it can be stored in a
 * separate area etc.
 *
 * Format of the kallsyms section.
 *
 * Header:
 *   Size of header.
 *   Total size of kallsyms data, including strings.
 *   Number of loaded sections.
 *   Offset to first section entry from start of header.
 *   Size of each section entry, excluding the name string.
 *   Number of symbols.
 *   Offset to first symbol entry from start of header.
 *   Size of each symbol entry, excluding the name string.
 *
 * Section entry - one per loaded section.
 *   Start of section[1].
 *   Size of section.
 *   Offset to name of section, from start of strings.
 *   Section flags.
 *
 * Symbol entry - one per symbol in the input file[2].
 *   Offset of section that owns this symbol, from start of section data.
 *   Address of symbol within the real section[1].
 *   Offset to name of symbol, from start of strings.
 *
 * Notes: [1] This is an exception to the "represent pointers as
 *            offsets" rule, it is a value, not an offset.  The start
 *            address of a section or a symbol is extracted from the
 *            obj_file data which may contain absolute or relocatable
 *            addresses.  If the addresses are relocatable then the
 *            caller must adjust the section and/or symbol entries in
 *            kallsyms after relocation.
 *        [2] Only symbols that fall within loaded sections are stored.
 */

int
obj_kallsyms (struct obj_file *fin, struct obj_file **fout_result)
{
    struct obj_file *fout;
    int i, loaded = 0, *fin_to_allsym_map;
    struct obj_section *isec, *osec;
    struct kallsyms_header *a_hdr;
    struct kallsyms_section *a_sec;
    ElfW(Off) sec_off;
    struct kallsyms_symbol *symbols = NULL, a_sym;
    ElfW(Word) symbols_size = 0, symbols_left = 0;
    char *strings = NULL, *p;
    ElfW(Word) strings_size = 0, strings_left = 0;
    ElfW(Off) file_offset;
    static char strtab[] = "\000" KALLSYMS_SEC_NAME;

    /* Create the kallsyms section.  */
    fout = arch_new_file();
    memset(fout, 0, sizeof(*fout));
    fout->symbol_cmp = strcmp;
    fout->symbol_hash = obj_elf_hash;
    fout->load_order_search_start = &fout->load_order;

    /* Copy file characteristics from input file and modify to suit */
    memcpy(&fout->header, &fin->header, sizeof(fout->header));
    fout->header.e_type = ET_REL;		/* Output is relocatable */
    fout->header.e_entry = 0;			/* No entry point */
    fout->header.e_phoff = 0;			/* No program header */
    file_offset = sizeof(fout->header);		/* Step over Elf header */
    fout->header.e_shoff = file_offset;		/* Section headers next */
    fout->header.e_phentsize = 0;		/* No program header */
    fout->header.e_phnum = 0;			/* No program header */
    fout->header.e_shnum = KALLSYMS_IDX+1;	/* Initial, strtab, kallsyms */
    fout->header.e_shstrndx = KALLSYMS_IDX-1;	/* strtab */
    file_offset += fout->header.e_shentsize * fout->header.e_shnum;

    /* Populate the section data for kallsyms itself */
    fout->sections = xmalloc(sizeof(*(fout->sections))*fout->header.e_shnum);
    memset(fout->sections, 0, sizeof(*(fout->sections))*fout->header.e_shnum);

    fout->sections[0] = osec = arch_new_section();
    memset(osec, 0, sizeof(*osec));
    osec->header.sh_type = SHT_NULL;
    osec->header.sh_link = SHN_UNDEF;

    fout->sections[KALLSYMS_IDX-1] = osec = arch_new_section();
    memset(osec, 0, sizeof(*osec));
    osec->name = ".strtab";
    osec->header.sh_type = SHT_STRTAB;
    osec->header.sh_link = SHN_UNDEF;
    osec->header.sh_offset = file_offset;
    osec->header.sh_size = sizeof(strtab);
    osec->contents = xmalloc(sizeof(strtab));
    memcpy(osec->contents, strtab, sizeof(strtab));
    file_offset += osec->header.sh_size;

    fout->sections[KALLSYMS_IDX] = osec = arch_new_section();
    memset(osec, 0, sizeof(*osec));
    osec->name = KALLSYMS_SEC_NAME;
    osec->header.sh_name = 1;			/* Offset in strtab */
    osec->header.sh_type = SHT_PROGBITS;	/* Load it */
    osec->header.sh_flags = SHF_ALLOC;		/* Read only data */
    osec->header.sh_link = SHN_UNDEF;
    osec->header.sh_addralign = sizeof(ElfW(Word));
    file_offset = (file_offset + osec->header.sh_addralign - 1)
	& -(osec->header.sh_addralign);
    osec->header.sh_offset = file_offset;

    /* How many loaded sections are there? */
    for (i = 0; i < fin->header.e_shnum; ++i) {
	if (fin->sections[i]->header.sh_flags & SHF_ALLOC)
	    ++loaded;
    }

    /* Initial contents, header + one entry per input section.  No strings. */
    osec->header.sh_size = sizeof(*a_hdr) + loaded*sizeof(*a_sec);
    a_hdr = (struct kallsyms_header *) osec->contents =
    	xmalloc(osec->header.sh_size);
    memset(osec->contents, 0, osec->header.sh_size);
    a_hdr->size = sizeof(*a_hdr);
    a_hdr->sections = loaded;
    a_hdr->section_off = a_hdr->size;
    a_hdr->section_size = sizeof(*a_sec);
    a_hdr->symbol_off = osec->header.sh_size;
    a_hdr->symbol_size = sizeof(a_sym);
    a_hdr->start = (ElfW(Addr))(~0);

    /* Map input section numbers to kallsyms section offsets. */
    sec_off = 0;	/* Offset to first kallsyms section entry */
    fin_to_allsym_map = xmalloc(sizeof(*fin_to_allsym_map)*fin->header.e_shnum);
    for (i = 0; i < fin->header.e_shnum; ++i) {
	isec = fin->sections[i];
	if (isec->header.sh_flags & SHF_ALLOC) {
	    fin_to_allsym_map[isec->idx] = sec_off;
	    sec_off += a_hdr->section_size;
	}
	else
	    fin_to_allsym_map[isec->idx] = -1;	/* Ignore this section */
    }

    /* Copy the loaded section data. */
    a_sec = (struct kallsyms_section *) ((char *) a_hdr + a_hdr->section_off);
    for (i = 0; i < fin->header.e_shnum; ++i) {
	isec = fin->sections[i];
	if (!(isec->header.sh_flags & SHF_ALLOC))
	    continue;
	a_sec->start = isec->header.sh_addr;
	a_sec->size = isec->header.sh_size;
	a_sec->flags = isec->header.sh_flags;
	a_sec->name_off = strings_size - strings_left;
	append_string(isec->name, &strings, &strings_size, &strings_left);
	if (a_sec->start < a_hdr->start)
	    a_hdr->start = a_sec->start;
	if (a_sec->start+a_sec->size > a_hdr->end)
	    a_hdr->end = a_sec->start+a_sec->size;
	++a_sec;
    }

    /* Build the kallsyms symbol table from the symbol hashes. */
    for (i = 0; i < HASH_BUCKETS; ++i) {
	struct obj_symbol *sym = fin->symtab[i];
	for (sym = fin->symtab[i]; sym ; sym = sym->next) {
	    if (!sym || sym->secidx >= fin->header.e_shnum)
		continue;
	    if ((a_sym.section_off = fin_to_allsym_map[sym->secidx]) == -1)
		continue;
	    if (strcmp(sym->name, "gcc2_compiled.") == 0 ||
		strncmp(sym->name, "__insmod_", 9) == 0)
		continue;
	    a_sym.symbol_addr = sym->value;
	    if (fin->header.e_type == ET_REL)
		a_sym.symbol_addr += fin->sections[sym->secidx]->header.sh_addr;
	    a_sym.name_off = strings_size - strings_left;
	    append_symbol(&a_sym, &symbols, &symbols_size, &symbols_left);
	    append_string(sym->name, &strings, &strings_size, &strings_left);
	    ++a_hdr->symbols;
	}
    }
    free(fin_to_allsym_map);

    /* Sort the symbols into ascending order by address and name */
    sym_strings = strings;	/* For symbol_compare */
    qsort((char *) symbols, (unsigned) a_hdr->symbols,
	sizeof(* symbols), symbol_compare);
    sym_strings = NULL;

    /* Put the lot together */
    osec->header.sh_size = a_hdr->total_size =
	a_hdr->symbol_off +
	a_hdr->symbols*a_hdr->symbol_size +
	strings_size - strings_left;
    a_hdr = (struct kallsyms_header *) osec->contents =
	xrealloc(a_hdr, a_hdr->total_size);
    p = (char *)a_hdr + a_hdr->symbol_off;
    memcpy(p, symbols, a_hdr->symbols*a_hdr->symbol_size);
    free(symbols);
    p += a_hdr->symbols*a_hdr->symbol_size;
    a_hdr->string_off = p - (char *)a_hdr;
    memcpy(p, strings, strings_size - strings_left);
    free(strings);

    *fout_result = fout;
    return 0;
}