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
;
30 static char _gp_disp_dummy
;
32 /* Check if EHDR is a valid ELF header. */
34 grub_arch_dl_check_header (void *ehdr
)
38 /* Check the magic numbers. */
39 #ifdef GRUB_CPU_WORDS_BIGENDIAN
40 if (e
->e_ident
[EI_CLASS
] != ELFCLASS32
41 || e
->e_ident
[EI_DATA
] != ELFDATA2MSB
42 || e
->e_machine
!= EM_MIPS
)
44 if (e
->e_ident
[EI_CLASS
] != ELFCLASS32
45 || e
->e_ident
[EI_DATA
] != ELFDATA2LSB
46 || e
->e_machine
!= EM_MIPS
)
48 return grub_error (GRUB_ERR_BAD_OS
, N_("invalid arch-dependent ELF magic"));
53 #pragma GCC diagnostic ignored "-Wcast-align"
56 grub_arch_dl_get_tramp_got_size (const void *ehdr
, grub_size_t
*tramp
,
59 const Elf_Ehdr
*e
= ehdr
;
61 /* FIXME: suboptimal. */
62 grub_size_t gp_size
= 0;
68 for (i
= 0, s
= (const Elf_Shdr
*) ((const char *) e
+ e
->e_shoff
);
70 i
++, s
= (const Elf_Shdr
*) ((const char *) s
+ e
->e_shentsize
))
71 if (s
->sh_type
== SHT_REL
)
73 const Elf_Rel
*rel
, *max
;
75 for (rel
= (const Elf_Rel
*) ((const char *) e
+ s
->sh_offset
),
76 max
= rel
+ s
->sh_size
/ s
->sh_entsize
;
79 switch (ELF_R_TYPE (rel
->r_info
))
89 if (gp_size
> 0x08000)
90 return grub_error (GRUB_ERR_OUT_OF_RANGE
, "__gnu_local_gp is too big\n");
97 /* Relocate symbols. */
99 grub_arch_dl_relocate_symbols (grub_dl_t mod
, void *ehdr
,
100 Elf_Shdr
*s
, grub_dl_segment_t seg
)
111 for (i
= 0, ri
= (Elf_Shdr
*) ((char *) ehdr
+ e
->e_shoff
);
113 i
++, ri
= (Elf_Shdr
*) ((char *) ri
+ e
->e_shentsize
))
114 if (ri
->sh_type
== SHT_MIPS_REGINFO
)
117 return grub_error (GRUB_ERR_BAD_MODULE
, "no reginfo found");
118 mod
->reginfo
= (grub_uint32_t
*)((char *) ehdr
+ ri
->sh_offset
);
121 gp0
= mod
->reginfo
[5];
124 for (rel
= (Elf_Rel
*) ((char *) e
+ s
->sh_offset
),
125 max
= (Elf_Rel
*) ((char *) rel
+ s
->sh_size
);
127 rel
= (Elf_Rel
*) ((char *) rel
+ s
->sh_entsize
))
131 grub_uint32_t sym_value
;
133 if (seg
->size
< rel
->r_offset
)
134 return grub_error (GRUB_ERR_BAD_MODULE
,
135 "reloc offset is out of the segment");
137 addr
= (grub_uint8_t
*) ((char *) seg
->addr
+ rel
->r_offset
);
138 sym
= (Elf_Sym
*) ((char *) mod
->symtab
139 + mod
->symsize
* ELF_R_SYM (rel
->r_info
));
140 sym_value
= sym
->st_value
;
141 if (s
->sh_type
== SHT_RELA
)
143 sym_value
+= ((Elf_Rela
*) rel
)->r_addend
;
145 if (sym_value
== (grub_addr_t
) &__gnu_local_gp_dummy
)
146 sym_value
= (grub_addr_t
) mod
->got
;
147 else if (sym_value
== (grub_addr_t
) &_gp_disp_dummy
)
149 sym_value
= (grub_addr_t
) mod
->got
- (grub_addr_t
) addr
;
150 if (ELF_R_TYPE (rel
->r_info
) == R_MIPS_LO16
)
151 /* ABI mandates +4 even if partner lui doesn't
152 immediately precede addiu. */
155 switch (ELF_R_TYPE (rel
->r_info
))
162 #ifdef GRUB_CPU_WORDS_BIGENDIAN
166 /* Handle partner lo16 relocation. Lower part is
167 treated as signed. Hence add 0x8000 to compensate.
169 value
= (*(grub_uint16_t
*) addr
<< 16)
170 + sym_value
+ 0x8000;
171 for (rel2
= rel
+ 1; rel2
< max
; rel2
++)
172 if (ELF_R_SYM (rel2
->r_info
)
173 == ELF_R_SYM (rel
->r_info
)
174 && ELF_R_TYPE (rel2
->r_info
) == R_MIPS_LO16
)
176 value
+= *(grub_int16_t
*)
177 ((char *) seg
->addr
+ rel2
->r_offset
178 #ifdef GRUB_CPU_WORDS_BIGENDIAN
184 *(grub_uint16_t
*) addr
= (value
>> 16) & 0xffff;
188 #ifdef GRUB_CPU_WORDS_BIGENDIAN
191 *(grub_uint16_t
*) addr
+= sym_value
& 0xffff;
194 *(grub_uint32_t
*) addr
+= sym_value
;
197 *(grub_uint32_t
*) addr
= sym_value
198 + *(grub_uint32_t
*) addr
+ gp0
- (grub_uint32_t
)mod
->got
;
205 raw
= (*(grub_uint32_t
*) addr
) & 0x3ffffff;
208 raw
= (value
>> 2) & 0x3ffffff;
210 *(grub_uint32_t
*) addr
=
211 raw
| ((*(grub_uint32_t
*) addr
) & 0xfc000000);
215 if (ELF_ST_BIND (sym
->st_info
) == STB_LOCAL
)
218 /* Handle partner lo16 relocation. Lower part is
219 treated as signed. Hence add 0x8000 to compensate.
221 sym_value
+= (*(grub_uint16_t
*) addr
<< 16)
223 for (rel2
= rel
+ 1; rel2
< max
; rel2
++)
224 if (ELF_R_SYM (rel2
->r_info
)
225 == ELF_R_SYM (rel
->r_info
)
226 && ELF_R_TYPE (rel2
->r_info
) == R_MIPS_LO16
)
228 sym_value
+= *(grub_int16_t
*)
229 ((char *) seg
->addr
+ rel2
->r_offset
230 #ifdef GRUB_CPU_WORDS_BIGENDIAN
236 sym_value
&= 0xffff0000;
237 *(grub_uint16_t
*) addr
= 0;
242 grub_uint32_t
*gpptr
= mod
->gotptr
;
244 #ifdef GRUB_CPU_WORDS_BIGENDIAN
247 *gpptr
= sym_value
+ *(grub_uint16_t
*) addr
;
248 *(grub_uint16_t
*) addr
249 = sizeof (grub_uint32_t
) * (gpptr
- (grub_uint32_t
*) mod
->got
);
250 mod
->gotptr
= gpptr
+ 1;
257 return grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET
,
258 N_("relocation 0x%x is not implemented yet"),
259 ELF_R_TYPE (rel
->r_info
));
265 return GRUB_ERR_NONE
;
269 grub_arch_dl_init_linker (void)
271 grub_dl_register_symbol ("__gnu_local_gp", &__gnu_local_gp_dummy
, 0, 0);
272 grub_dl_register_symbol ("_gp_disp", &_gp_disp_dummy
, 0, 0);