special.c 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194
  1. /*
  2. * Copyright (C) 2015 Josh Poimboeuf <jpoimboe@redhat.com>
  3. *
  4. * This program is free software; you can redistribute it and/or
  5. * modify it under the terms of the GNU General Public License
  6. * as published by the Free Software Foundation; either version 2
  7. * of the License, or (at your option) any later version.
  8. *
  9. * This program is distributed in the hope that it will be useful,
  10. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. * GNU General Public License for more details.
  13. *
  14. * You should have received a copy of the GNU General Public License
  15. * along with this program; if not, see <http://www.gnu.org/licenses/>.
  16. */
  17. /*
  18. * This file reads all the special sections which have alternate instructions
  19. * which can be patched in or redirected to at runtime.
  20. */
  21. #include <stdlib.h>
  22. #include <string.h>
  23. #include "special.h"
  24. #include "warn.h"
  25. #define EX_ENTRY_SIZE 12
  26. #define EX_ORIG_OFFSET 0
  27. #define EX_NEW_OFFSET 4
  28. #define JUMP_ENTRY_SIZE 24
  29. #define JUMP_ORIG_OFFSET 0
  30. #define JUMP_NEW_OFFSET 8
  31. #define ALT_ENTRY_SIZE 13
  32. #define ALT_ORIG_OFFSET 0
  33. #define ALT_NEW_OFFSET 4
  34. #define ALT_FEATURE_OFFSET 8
  35. #define ALT_ORIG_LEN_OFFSET 10
  36. #define ALT_NEW_LEN_OFFSET 11
  37. #define X86_FEATURE_POPCNT (4*32+23)
  38. struct special_entry {
  39. const char *sec;
  40. bool group, jump_or_nop;
  41. unsigned char size, orig, new;
  42. unsigned char orig_len, new_len; /* group only */
  43. unsigned char feature; /* ALTERNATIVE macro CPU feature */
  44. };
  45. struct special_entry entries[] = {
  46. {
  47. .sec = ".altinstructions",
  48. .group = true,
  49. .size = ALT_ENTRY_SIZE,
  50. .orig = ALT_ORIG_OFFSET,
  51. .orig_len = ALT_ORIG_LEN_OFFSET,
  52. .new = ALT_NEW_OFFSET,
  53. .new_len = ALT_NEW_LEN_OFFSET,
  54. .feature = ALT_FEATURE_OFFSET,
  55. },
  56. {
  57. .sec = "__jump_table",
  58. .jump_or_nop = true,
  59. .size = JUMP_ENTRY_SIZE,
  60. .orig = JUMP_ORIG_OFFSET,
  61. .new = JUMP_NEW_OFFSET,
  62. },
  63. {
  64. .sec = "__ex_table",
  65. .size = EX_ENTRY_SIZE,
  66. .orig = EX_ORIG_OFFSET,
  67. .new = EX_NEW_OFFSET,
  68. },
  69. {},
  70. };
  71. static int get_alt_entry(struct elf *elf, struct special_entry *entry,
  72. struct section *sec, int idx,
  73. struct special_alt *alt)
  74. {
  75. struct rela *orig_rela, *new_rela;
  76. unsigned long offset;
  77. offset = idx * entry->size;
  78. alt->group = entry->group;
  79. alt->jump_or_nop = entry->jump_or_nop;
  80. if (alt->group) {
  81. alt->orig_len = *(unsigned char *)(sec->data->d_buf + offset +
  82. entry->orig_len);
  83. alt->new_len = *(unsigned char *)(sec->data->d_buf + offset +
  84. entry->new_len);
  85. }
  86. if (entry->feature) {
  87. unsigned short feature;
  88. feature = *(unsigned short *)(sec->data->d_buf + offset +
  89. entry->feature);
  90. /*
  91. * It has been requested that we don't validate the !POPCNT
  92. * feature path which is a "very very small percentage of
  93. * machines".
  94. */
  95. if (feature == X86_FEATURE_POPCNT)
  96. alt->skip_orig = true;
  97. }
  98. orig_rela = find_rela_by_dest(sec, offset + entry->orig);
  99. if (!orig_rela) {
  100. WARN_FUNC("can't find orig rela", sec, offset + entry->orig);
  101. return -1;
  102. }
  103. if (orig_rela->sym->type != STT_SECTION) {
  104. WARN_FUNC("don't know how to handle non-section rela symbol %s",
  105. sec, offset + entry->orig, orig_rela->sym->name);
  106. return -1;
  107. }
  108. alt->orig_sec = orig_rela->sym->sec;
  109. alt->orig_off = orig_rela->addend;
  110. if (!entry->group || alt->new_len) {
  111. new_rela = find_rela_by_dest(sec, offset + entry->new);
  112. if (!new_rela) {
  113. WARN_FUNC("can't find new rela",
  114. sec, offset + entry->new);
  115. return -1;
  116. }
  117. alt->new_sec = new_rela->sym->sec;
  118. alt->new_off = (unsigned int)new_rela->addend;
  119. /* _ASM_EXTABLE_EX hack */
  120. if (alt->new_off >= 0x7ffffff0)
  121. alt->new_off -= 0x7ffffff0;
  122. }
  123. return 0;
  124. }
  125. /*
  126. * Read all the special sections and create a list of special_alt structs which
  127. * describe all the alternate instructions which can be patched in or
  128. * redirected to at runtime.
  129. */
  130. int special_get_alts(struct elf *elf, struct list_head *alts)
  131. {
  132. struct special_entry *entry;
  133. struct section *sec;
  134. unsigned int nr_entries;
  135. struct special_alt *alt;
  136. int idx, ret;
  137. INIT_LIST_HEAD(alts);
  138. for (entry = entries; entry->sec; entry++) {
  139. sec = find_section_by_name(elf, entry->sec);
  140. if (!sec)
  141. continue;
  142. if (sec->len % entry->size != 0) {
  143. WARN("%s size not a multiple of %d",
  144. sec->name, entry->size);
  145. return -1;
  146. }
  147. nr_entries = sec->len / entry->size;
  148. for (idx = 0; idx < nr_entries; idx++) {
  149. alt = malloc(sizeof(*alt));
  150. if (!alt) {
  151. WARN("malloc failed");
  152. return -1;
  153. }
  154. memset(alt, 0, sizeof(*alt));
  155. ret = get_alt_entry(elf, entry, sec, idx, alt);
  156. if (ret)
  157. return ret;
  158. list_add_tail(&alt->list, alts);
  159. }
  160. }
  161. return 0;
  162. }