1 /* dl-386.c - arch-dependent part of loadable module support */
3 * GRUB -- GRand Unified Bootloader
4 * Copyright (C) 2002,2005,2007,2009 Free Software Foundation, Inc.
6 * GRUB is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
11 * GRUB is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with GRUB. If not, see <http://www.gnu.org/licenses/>.
22 #include <grub/misc.h>
24 #include <grub/cpu/types.h>
26 #include <grub/i18n.h>
28 /* Dummy __gnu_local_gp. Resolved by linker. */
29 static char __gnu_local_gp_dummy
;
31 /* Check if EHDR is a valid ELF header. */
33 grub_arch_dl_check_header (void *ehdr
)
37 /* Check the magic numbers. */
38 #ifdef GRUB_CPU_WORDS_BIGENDIAN
39 if (e
->e_ident
[EI_CLASS
] != ELFCLASS32
40 || e
->e_ident
[EI_DATA
] != ELFDATA2MSB
41 || e
->e_machine
!= EM_MIPS
)
43 if (e
->e_ident
[EI_CLASS
] != ELFCLASS32
44 || e
->e_ident
[EI_DATA
] != ELFDATA2LSB
45 || e
->e_machine
!= EM_MIPS
)
47 return grub_error (GRUB_ERR_BAD_OS
, N_("invalid arch-dependent ELF magic"));
52 #pragma GCC diagnostic ignored "-Wcast-align"
54 /* Relocate symbols. */
56 grub_arch_dl_relocate_symbols (grub_dl_t mod
, void *ehdr
)
62 grub_size_t gp_size
= 0;
63 /* FIXME: suboptimal. */
64 grub_uint32_t
*gp
, *gpptr
;
67 /* Find a symbol table. */
68 for (i
= 0, s
= (Elf_Shdr
*) ((char *) e
+ e
->e_shoff
);
70 i
++, s
= (Elf_Shdr
*) ((char *) s
+ e
->e_shentsize
))
71 if (s
->sh_type
== SHT_SYMTAB
)
75 return grub_error (GRUB_ERR_BAD_MODULE
, N_("no symbol table"));
77 entsize
= s
->sh_entsize
;
80 for (i
= 0, s
= (Elf_Shdr
*) ((char *) e
+ e
->e_shoff
);
82 i
++, s
= (Elf_Shdr
*) ((char *) s
+ e
->e_shentsize
))
83 if (s
->sh_type
== SHT_MIPS_REGINFO
)
87 return grub_error (GRUB_ERR_BAD_MODULE
, "no reginfo found");
89 gp0
= ((grub_uint32_t
*)((char *) e
+ s
->sh_offset
))[5];
91 for (i
= 0, s
= (Elf_Shdr
*) ((char *) e
+ e
->e_shoff
);
93 i
++, s
= (Elf_Shdr
*) ((char *) s
+ e
->e_shentsize
))
94 if (s
->sh_type
== SHT_REL
)
96 grub_dl_segment_t seg
;
98 /* Find the target segment. */
99 for (seg
= mod
->segment
; seg
; seg
= seg
->next
)
100 if (seg
->section
== s
->sh_info
)
107 for (rel
= (Elf_Rel
*) ((char *) e
+ s
->sh_offset
),
108 max
= rel
+ s
->sh_size
/ s
->sh_entsize
;
111 switch (ELF_R_TYPE (rel
->r_info
))
122 if (gp_size
> 0x08000)
123 return grub_error (GRUB_ERR_OUT_OF_RANGE
, "__gnu_local_gp is too big\n");
125 gpptr
= gp
= grub_malloc (gp_size
);
129 for (i
= 0, s
= (Elf_Shdr
*) ((char *) e
+ e
->e_shoff
);
131 i
++, s
= (Elf_Shdr
*) ((char *) s
+ e
->e_shentsize
))
132 if (s
->sh_type
== SHT_REL
)
134 grub_dl_segment_t seg
;
136 /* Find the target segment. */
137 for (seg
= mod
->segment
; seg
; seg
= seg
->next
)
138 if (seg
->section
== s
->sh_info
)
145 for (rel
= (Elf_Rel
*) ((char *) e
+ s
->sh_offset
),
146 max
= rel
+ s
->sh_size
/ s
->sh_entsize
;
153 if (seg
->size
< rel
->r_offset
)
154 return grub_error (GRUB_ERR_BAD_MODULE
,
155 "reloc offset is out of the segment");
157 addr
= (grub_uint8_t
*) ((char *) seg
->addr
+ rel
->r_offset
);
158 sym
= (Elf_Sym
*) ((char *) mod
->symtab
159 + entsize
* ELF_R_SYM (rel
->r_info
));
160 if (sym
->st_value
== (grub_addr_t
) &__gnu_local_gp_dummy
)
161 sym
->st_value
= (grub_addr_t
) gp
;
163 switch (ELF_R_TYPE (rel
->r_info
))
170 #ifdef GRUB_CPU_WORDS_BIGENDIAN
174 /* Handle partner lo16 relocation. Lower part is
175 treated as signed. Hence add 0x8000 to compensate.
177 value
= (*(grub_uint16_t
*) addr
<< 16)
178 + sym
->st_value
+ 0x8000;
179 for (rel2
= rel
+ 1; rel2
< max
; rel2
++)
180 if (ELF_R_SYM (rel2
->r_info
)
181 == ELF_R_SYM (rel
->r_info
)
182 && ELF_R_TYPE (rel2
->r_info
) == R_MIPS_LO16
)
184 value
+= *(grub_int16_t
*)
185 ((char *) seg
->addr
+ rel2
->r_offset
186 #ifdef GRUB_CPU_WORDS_BIGENDIAN
192 *(grub_uint16_t
*) addr
= (value
>> 16) & 0xffff;
196 #ifdef GRUB_CPU_WORDS_BIGENDIAN
199 *(grub_uint16_t
*) addr
+= (sym
->st_value
) & 0xffff;
202 *(grub_uint32_t
*) addr
+= sym
->st_value
;
205 *(grub_uint32_t
*) addr
= sym
->st_value
206 + *(grub_uint32_t
*) addr
+ gp0
- (grub_uint32_t
)gp
;
213 raw
= (*(grub_uint32_t
*) addr
) & 0x3ffffff;
215 value
+= sym
->st_value
;
216 raw
= (value
>> 2) & 0x3ffffff;
218 *(grub_uint32_t
*) addr
=
219 raw
| ((*(grub_uint32_t
*) addr
) & 0xfc000000);
225 #ifdef GRUB_CPU_WORDS_BIGENDIAN
228 *gpptr
= sym
->st_value
+ *(grub_uint16_t
*) addr
;
229 *(grub_uint16_t
*) addr
230 = sizeof (grub_uint32_t
) * (gpptr
- gp
);
238 return grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET
,
239 N_("relocation 0x%x is not implemented yet"),
240 ELF_R_TYPE (rel
->r_info
));
248 return GRUB_ERR_NONE
;
252 grub_arch_dl_init_linker (void)
254 grub_dl_register_symbol ("__gnu_local_gp", &__gnu_local_gp_dummy
, 0, 0);